
The use of `class` for things that should be simple free functions - ingve
https://quuxplusone.github.io/blog/2020/05/28/oo-antipattern/
======
mehrdadn
Counter-point: while in many situations this isn't the right approach, it's
worth recognizing when this _is_ the right approach, because they can look
awfully similar. An example of this is graph searching, e.g. BFS or Dijkstra's
algorithm. The typical implementation is a function. But if you make Dijkstra
a class, with (say) a function to iterate through nodes, it lets you do
several things that would be difficult with a function: (a) you can now re-use
the same object for computing distances to multiple vertices, which allows
incremental search, (b) you can now fork/copy the object and continue
searching on _different_ extensions of the original graph, (c) you can now
save & restore the searcher state to pause the search & continue it later, (d)
you can do multiple searches across one or more graphs _in lockstep_. IMHO
these are very much _not_ obvious, and to someone who doesn't recognize what's
going on, a class for something like BFS will look _exactly_ like an "OO
antipattern", when in reality it's actually providing significant additional
functionality for more complicated situations.

~~~
amw-zero
None of these situations require an object to do. You can always have a
function that takes in an extra ‘state’ argument. You can then do everything
you said by passing in the corresponding state values.

~~~
JoeAltmaier
Sure, you can build your own object and pass the 'this' pointer around
manually. You could use C for everything, and tediously do everything again
that objects do for free.

But why?

There's a lot of object-hating going around. Its silly. Use objects to
encapsulate functionality, they are good at that and everybody understands
what it means.

~~~
amw-zero
You don’t agree with an idea so it’s silly? Passing around state via variables
is no more tedious than the extra syntax for class definitions, constructors,
member variable access, extra semantics related to objects, extra keywords
related to visibility, etc.

You can encapsulate functionality without objects. There is nothing that you
can do with an object that you can’t do with a closure, and the closure
version will be 1/10th the size and have 1/10th the semantic programming
language elements. That is why you see ‘object-hating’ all over the industry.
It hasn’t lived up to its promise.

Objects aren’t simpler, inherently. It’s just what you know, today, and you’re
unwilling to acknowledge your biases.

~~~
woah
Seems like kind of a meaningless gesture to pass in your own “this” just
because you don’t like OOP. If you need a class, why not write it the
idiomatic way?

~~~
nightski
I didn't down-vote you, but in my opinion it's not meaningless because it
makes it inherently more testable. Try testing an opaque class without any
accessibility into its state. It is much more difficult. If it takes in the
state, performs an operation, and returns a new version of that state (ideally
an immutable copy) then it becomes much easier to test and validate.

The reason information hiding is/was advocated was for reduced coupling. But
in reality I find that this coupling becomes implicit which is arguably worse.
But this is just my opinion.

~~~
munchbunny
I see your point, but I 80% disagree with your conclusion.

When you test opaque objects, you're generally not trying to test individual
state transitions, you're trying to test that the class's interface adheres to
the external promises it makes.

That said, many OOP languages have solutions specifically for when you
actually do need to test those internal state transitions. C# for example has
the "internal" keyword and allows you to declare friend assemblies, so you
mostly get your cake and eat it too, at the cost of not hiding the code from
yourself as the module implementer.

~~~
NoodleIncident
I interpreted the top-level comment as a list of extra external promises you
could add to your class's interface (incremental search, save/restore, etc).
If those are easier to test with functions and a state parameter, it might be
better to skip the class.

------
AlexanderDhoore
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.

~~~
savingGrace
I felt as though I should know the moral of the story, yet I did not.

[https://stackoverflow.com/a/11421598](https://stackoverflow.com/a/11421598)
Moral of the story is that closures and objects are ideas that are expressible
in terms of each other, and none is more fundamental than the other. That's
all there is to the statement under consideration.

~~~
gfxgirl
Maybe I'm mis-understanding the definition of class or object but isn't a
"File" in even Haskell an example of a class. Files have state which is either
the read head or write head and in most languages you can change the head
using seek.

I don't think the fact that you call `file.seek` vs `seek(file)` the
difference between functional and object oriented. functional people say
"state = evil, side effects = evil" and yet that version of `seek` has both.

The functional version of files would have to move the head out of the file

newWritePosition = write(file, currrentWritePosition)

And there would be no need for seek since you're holding your own write and/or
read position.

If you close over a bunch of state and pass back a function or functions to
work with that closed state you've just created an object with state and side
effects. That's exactly what FP people say is bad.

~~~
wwright
I can’t speak for all functional programming advocates, but personally, when I
criticize “classes” it is because of the overall design/culture and not
because of encapsulating state/side effects.

\- Subclassing is notoriously misleading and confusing and almost never truly
useful (true subclassing, not abstract subclassing/interfaces).

\- Class syntax/semantics are usually divorced from the other semantics of the
language; closures are usually a much simpler design to achieve the same
power. (You can get class-like ideas with simpler ideas too, but I think most
“class” features don’t.)

\- Culturally, classes are often viewed as mythic/special in a way that’s kind
of out of touch with mapping problems to solutions. Look at “typical” Java
code full of classes for crazy tasks like implementing 20 getters and
constructing a factory to create callbacks. All of these things are related to
the problem, but there’s a lot of friction/mismatch because people are taught
to “use classes.”

------
makapuf
Also known in python as "if your class has only two methods, one of which is
init, it's a function" in the "stop writing classes"
[https://www.youtube.com/watch?v=o9pEzgHorH0](https://www.youtube.com/watch?v=o9pEzgHorH0)

EDIT: typo, changed link

~~~
twic
I have a few little classes that are clients for network services. They have a
constructor which sets up an HTTP client or socket or something, and maybe
prepares some metadata, and then a method to make a call to the service.

I could write these clients as lambdas or nested functions which close over
the stuff which init creates. But why? An object makes it much clearer that
there is state.

~~~
shoo
I agree. I think of this as a functor pattern: object that supports some
params being bound at constructor time, and other params set when the function
is called later.

In languages that let you operator overload function call syntax, you end up
with an object that, once constructed, supports being called like with same
syntax as a function call. This works easily in python (define __init__ and
__call__ ), and you don't have to fight the typesystem to structure code that
will accept both a callable object or a function.

Another perspective of the whole thing is that you have a function with many
arguments, then you curry to bind some arguments, then pass the resulting
function with remaining free arguments to be called.

I prefer structuring code as functor objects as it lets you access the bound
parameters as attributes (if you want to expose them) which can also sometimes
be useful in code or in test

~~~
twic
> I agree. I think of this as a functor pattern: object that supports some
> params being bound at constructor time, and other params set when the
> function is called later.

> Another perspective of the whole thing is that you have a function with many
> arguments, then you curry to bind some arguments, then pass the resulting
> function with remaining free arguments to be called.

It's not the same, though, because a socket etc is being constructed in the
constructor. Here's an abridged (and possibly wrong!) version of a monitoring
client:

    
    
        class Monitor:
            def __init__(self, monitoring_url, app_name):
                self.monitoring_url = monitoring_url
                self.session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=1))
                self.details = ujson.dumps({'app_name': app_name, 'pid': os.getpid()})
    
            async def send(self):
                async with self.session.post(monitoring_url, data=self.details, raise_for_status=True) as resp:
                    pass
    

If you treat that as currying, you will create a new ClientSession every time
you call ping(). A ClientSession contains a connection pool, so that means you
will create a new socket instead of reusing one.

> In languages that let you operator overload function call syntax, you end up
> with an object that, once constructed, supports being called like with same
> syntax as a function call. This works easily in python (define __init__ and
> __call__ ), and you don't have to fight the typesystem to structure code
> that will accept both a callable object or a function.

In Python and Java, you can easily refer to bound methods to produce callables
from objects, so this seems like unnecessary work.

------
smcl
I'm always curious when someone recommends reading The Elements of Programming
Style. I had seen so many recommendations that one day I decided to dive into
it and read through it all myself. The book is a fine read and an interesting
snapshot of what coding in 1974 was like. Seeing Kernighan and Plauger
analysing and understanding coding issues and formulating early coding best
practices is really interesting. However its value as a reference for modern
day programmers is heavily overstated - you'll spend most of your time staring
at big listings of early Fortran and PL/I code for one thing.

So read it, enjoy it, smile when you see guidance that's still applicable
today ... but do not expect to achieve any sort of enlightenment. And be wary
if anyone who tells you otherwise.

~~~
runawaybottle
I’d be wary just from the fact someone tried to piggy back off the
authoritative work of Elements of Style.

~~~
jholman
Elements of Style is not authoritative. Widely cited, widely praised, widely
criticized.

Find an author you enjoy, subject their work to an analysis strictly driven by
Elements of Style, and you'll find they fail to measure up. Even if the book
in question is Charlotte's Web, by the way.

I'm not saying the book has no value, but it's far from authoritative.

------
taneq
This reminds me of the blog rant "Execution in the Kingdom of Nouns":
[https://steve-yegge.blogspot.com/2006/03/execution-in-
kingdo...](https://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-
nouns.html)

~~~
pure-awesome
Luckily Java 8 introduced streams and functional interfaces - the latter of
which is admittedly very object-oriented way of passing around functions.

~~~
pure-awesome
For those wondering what I mean:

To support backwards-compatibility, Java could not simply introduce functions
as first-class objects. Instead, it works like follows:

Syntax for a function `foo` which itself takes in a function called
`intConcat` that takes in two ints and spits out string:

    
    
        foo(BiFunction<Integer, Integer, String> intConcat) {
            // ...
        }
    

Notice that I have to explicitly state "BiFunction". There is also "Function"
for single-argument functions, and nothing for more arguments (there are also
`Runnable`, `Consumer` and `Producer` for fewer arguments in-or-out). This is
because BiFunction isn't actually a function - it's an INTERFACE! Any class
that implements 'apply' and 'andThen' functions with the right signatures will
satisfy it, and can be passed in. You can make your own class and Java will
happily accept it into this method.

Java then just adds some nice syntactic sugar to make stuff look like
functions. E.g., if you do want to define an anonymous lambda like

    
    
        (x,y) -> "" + x + y;
    

What happens under-the-hood is that Java defines an anonymous BiFunction
class. You can assign it to a variable and do everything you would want to do
with an object:

    
    
        BiFunction<Integer, Integer, String> bif = (x, y) -> "" + x + y;
    

I can call bif.toString() and all those other default methods defined on
objects in Java. It's really not a function, it's an object holding a
function:

    
    
        BiFunction<Integer, Integer, String> {
    
            String apply(Integer x, Integer y) {
                return "" + x + y;
            }
    
            // ...
        }
    

and if you were to go and implement your own BiFunction as above (filling in
the blanks) - you could pass it around exactly the same places as your
"anonymous lambda" and it would work exactly the same way because it IS the
same thing.

Like I said, a very object-oriented approach to functionality.

------
cessor
Reducing OOP to implementation detail features will yield poor results. The
flexibility stems from an improved means of analysis in that types and
entities can be identified more easily. By using classes and objects you're
retaining flexibility because they can be interchanged; It's easy to create an
object that pretends to be a function; it's harder to make a function retain
state later.

The example is nice; it removes a lot of code but also removes the deferred
execution aspect of the solution. Calling `countDominoTilings` will make it
run in that instance; encapsulating the whole thing makes it able to pass it
around and execute the potentially expensive `.count` method later. If applied
properly, this can give you quite a lot of flexibility; for some memory and
performance tradeoff; but that's the cost of abstraction.

Instead of looking for how FP and OOP are dissimilar, why not start looking
for similarities instead? Objects are just higher order functions. Calling
ctors is just partial function application.

~~~
nine_k
(1) Interfaces / typeclasses / traits are good; inheritance is bad. An object
is a family of partially applied functions with some shared state; this means
tight coupling between them, rather often unwanted.

(2) Not having mutable state _is the point_. The more state you have, and the
more code paths can mutate the state, the harder it is to reason about the
program correctly. Examples of various degrees of hilarity / severity abound.

Sometimes shared mutable state is inevitable for performance reasons. It
should be well insulated from other parts of the program then. Same applies to
other effects, such as I/O. The standard approach is a core of pure functions
with an outer shell of effectful / stateful procedures (as opposed to a mix of
them).

While I'm on this soapbox, let me remind that Smalltalk was initially
envisioned as an actor system, something like Erlang (hence "messages"), but
hardware limitations did not allow to implement the original vision.

~~~
cessor
> inheritance is bad

That depends. Inheritance makes it easy to break encapsulation (which is bad
-- agreed). It can be hard to model "Is-A"-Relationships properly, but I
wouldn't call it inherently bad. A circle isn't an ellipsis; but a chair is
furniture. The quality of code stems from your quality of thought.

> this means tight coupling between them, rather often unwanted.

That depends on your design. Coupling is the whole point, you do objects
because you want to couple. Fraction.reduce, Fraction.add, Fraction.subtract,
Fraction.multiply. Why not have them as as a cohesive unit, all these
functions must understand the details of fraction anyway. Why not couple them?

> Not having mutable state is the point.

I agree. But objects never force you to publish their state; that's bad
education. Getters and setters should be avoided. The Fraction above can be
made immutable easily, Fraction(1, 2).add(Fraction(1,4)) --> <Fraction: 3/4>,
leaving the old ones intact.

But then, it depends on how you communicate state.

I believe `Connection.Open()` should return an Object of Type
`OpenConnection`. I believe that state changes should be communicated by
changes of identity (either a new instance or a new Type), but ideas like this
are most often answered by waving torches and pitchforks in front of my house
at night (figuratively).

~~~
echlebek
Inheritance reminds me of [https://www.pcgamer.com/heres-whats-happening-
inside-fallout...](https://www.pcgamer.com/heres-whats-happening-inside-
fallout-3s-metro-train/)

Your OpenConnection idea might make sense in some abstract way, but one thing
I know about connections is that they have a habit of closing. They will close
without any notice to your programming runtime, because the operating system
will do it. What happens to your OpenConnection object then? Well it becomes
invalidated, and now you have a nonsensical object hanging around. So you read
from it, get an error, and... now what? Replace it with a ClosedConnection?

~~~
cessor
> What happens to your OpenConnection object then?

It raises an exception. This interrupts the normal flow of things and asks you
to deal with the problem asap. If necessary, the handling code then could try
to reconnect or abort. If desired, it could return a closed connection to
convey that state-change, so that calling code is made aware that it needs to
reconnect first and can't just reused the now closed connection. You could
revert it to a normal connection (a "Has never ever been opened to begin
with"-Connection). Depends on whether your driver/adapter/underlying
connection thing cares about a difference in initial connects or reconnecting.
If the handling code can't deal with it, it can bubble the exception up one
level.

Swapping a `Connection` for an `OpenConnection` isn't heretic by the way, such
structures are described by the state pattern. Objects model state, Exceptions
model events (state transitions) but the later isn't explicitly described in
the original Gang of Four book that way. I just found that exceptions are very
usefull for this, given that you react to them in only a limited scope.

Be aware that this idea is culturally dependent. In Java, Exceptions are often
discouraged from being used in such a way (exceptions should never be used for
control flow and only convey error cases), in Python it's normal to
communicate state transition that way, e.g. for-loops watch for StopIteration
exceptions.

------
jcelerier
A lot of time where I just write simple functions, I end up having to wrap
them into objects because it is much more convenient when I want to do static
polymorphism - sure, that function I'm writing _now_ doesn't need state but
then two days after I have to refactor because the next strategy I use does
indeed require state. e.g. :

    
    
        template<typename F1, typename F2>
        struct MyAlgorithm {
            F1 f1; F2 f2;
            void operator()(...) { ... f1(whatever); ... f2(whatever); }
        };
    

you can't just pass functions to F1 or F2, they have to be objects.

I remember this happening to me at least half a dozen times so far so now I
just don't waste time and write them as structs with an operator() directly if
it's anything that is meant to be used as a callback somewhere.

See also item 46 on Scott Meyers' "Programming with the STL" which points out
a few other issues which can arise from using functions.

~~~
steerablesafe
> you can't just pass functions to F1 or F2, they have to be objects.

Why?

[https://godbolt.org/z/2kJsT9](https://godbolt.org/z/2kJsT9)

~~~
jkcxn
I'm new to C++, what are lines 11-12 for? I see that it doesn't compile
without it. It doesn't seem to be the new 'arrow return type' syntax because
there's no auto keyword.

Edit: I found it - it's a 'template deduction guide'
[https://stackoverflow.com/questions/40951697/what-are-
templa...](https://stackoverflow.com/questions/40951697/what-are-template-
deduction-guides-and-when-should-we-use-them)

~~~
gpderetta
Yes, the functionality was added in C++17, it is quite convenient, before you
had to write boilerplate make_X 'constructor' functions.

In C++20 this specific deduction guide is no longer required as it is
implicitly generated for template aggregates, so even less boilerplate.

------
arc-in-space
> std::cout << DominoTilingCounter(4, 7).count() << '\n';

> // Fails to compile!

What? But this literally does compile. The author is pretty well-versed in C++
so this one is a bit baffling. Just because it's a temporary doesn't mean it's
forced to be const or something.

------
jonnycat
The real meat of this post is:

> "No more class, no more worrying about const, no more worrying about
> memoization (it becomes the caller’s problem, for better or worse)."

By making the memoization the "caller's problem", this refactor has
_completely_ changed what the code does, for the sake of calling out "an OO
antipattern".

Yes, there are of course other ways to implement some form of memoization or
caching without classes. And without knowing the particular use case, it's
impossible to form an opinion on whether it _should_ be the caller's
responsibility or not. But I find it unconvincing to make an argument for
"here's a better way to solve problem X" and then present a solution for
solving problem Y.

~~~
Twisol
The memoization capability only disappears in the very last step, which I
would argue is _not_ the meat of the post. Up to that point, very valuable
refactorings and simplications have been applied.

One could very well have replaced the final transformation with a _factoring
out_ of the algorithm into a free function, which the constructor merely calls
and caches the result of, without meaningfully changing the point of the post.
(In fact, that would call out the memoization capability as an orthogonal
aspect even more strongly, since you could reuse the same construction for
memoizing other free functions.)

------
Jare
Single-function classes make sense if you want the computation to happen
lazily on-demand (it may be resource-intensive in CPU, GPU, storage, I/O,
etc). The class acts as the place to hold the parameters needed for it and the
result if/when it is computed. There's nothing wrong with that.

Down the line you may often need the capability to discard the computation to
save storage (and maybe re-do it at a later time), at which point it is not
even a single-function class anymore.

~~~
tom_mellior
> The class acts as the place to hold the parameters needed for it and the
> result if/when it is computed. There's nothing wrong with that.

Right, and the constness problem can be overcome by making some fields
mutable. This is exactly what "mutable" is for.

If the requirement is to have a lazy, memoized computation, then a class is
good. If the requirement is to have an eager, non-memoized computation, a
function is good. The article keeps shifting the goalposts and beating
strawmen that implement different specifications. It's not very good at making
the point it thinks it's making.

~~~
vlovich123
Mutable in C++11 land holds a weird space because the threading model says
that const member methods are thread safe, which mutable member variables are
not.

~~~
gpderetta
> threading model says that const member methods are thread safe

that's only true for standard library objects, although it is an useful
guideline for all code.

~~~
vlovich123
I don't think that's true.

> A C++ standard library function shall not directly or indirectly modify
> objects (1.10) accessible by threads other than the current thread unless
> the objects are accessed directly or indirectly via the function’s non-const
> arguments, including this

Consider the case where I invoke `std::find_if`. It takes const iterators to a
std::vector. I'm now indirectly modifying objects through standard library
functions that are modifying objects by multiple threads through const
arguments.

Pretty sure this is a viral requirement and using any part of the STL can
effectively taint your program if you're not careful.

------
earthboundkid
The zen is to realize that these are the exact same but totally different:

    
    
        myfunc(arg1, arg2)
        arg1.mymethod(arg2)
        myclosure(arg1)(arg2)

~~~
cessor
True. Python taught me that.

------
elpescado
Author makes some valid points, but jumps to conclusions too quickly. Most
importantly, code in examples that actually solves problem is omitted
(`[...recursive solution omitted...]`) - and that code can be potentially very
long. If you have very long function, you should break it into smaller
functions. But, those functions need to operate on shared state. In some
languages (Python) you can have nested functions that can operate on variables
defined in parent function, but in some other languages you can't, so you can
pass shared state in other ways, like function parameters or, wait for it,
what can you use when you have bunch of functions operating on some shared
state? Use classes! You can even wrap it simple function to have cleaner
interface.

Other counter examples might be Template Method pattern etc.

~~~
Tomis02
What's wrong with having the functions stateless and just passing the state in
as a parameter? OOP does exactly this behind the scenes.

> foo.do(bla)

is syntactic sugar for

> do(foo, bla)

------
thayne
I suspect the absense of free functions in Java (which is frequently the first
language taught at universities) is a big reason this antipattern is
prevalent.

~~~
themodelplumber
That brings back frustrating memories of moving to Java from day one at
university and trying to get up to speed. I remember a bunch of "tips"
spreading around the computer lab, all related to IDE and language quirks, in
a 100 level class. I hope this isn't still the case, 20 years later? It kind
of spoiled me for Java, which made life difficult later when I joined a FOSS
project which was built in Java.

After that experience I found returning not even just to functions but also
e.g. Pascal procedures was pretty refreshing.

------
gorgoiler
On the topic of eliminating simplistic classes: one of things an object is for
is to enclose multiple pieces of data and provide multiple methods that access
that data.

If one can compose ones problem into a lot of classes of object that only have
one method then it is quite acceptable to just use closures instead:

    
    
      HI = ‘Hello’
      def greeter(title, name):
        def f():
          print(HI, title, name)
        return f
    

I wrote a whole bunch of Python like this, yesterday. It felt liberating! So
terse!

Downside: one very common reason to have more than one method in a class is to
define __str__. There will be no pretty debug printable version of my
greeter() “class” for me, alas.

~~~
cessor
I like it. It gives you (f)actual private fields in python, as title and name
can't be accessed via the instance variable of f.

That also works in JavaScript:

    
    
        function Fraction(a, b) {
            this.value = function () {
                return a / b; 
            }
        }
    
        const half = new Fraction(1, 2);
    

It feels weird to me, that classes were eventually introduced in JS.

~~~
aikah
> It feels weird to me, that classes were eventually introduced in JS.

if you don't put value in the Fraction prototype, then value will be created
each time Fraction is instanced.

But that's not why class where created. Class simplify writing classes, doing
inheritance and introduce "super" static binding which wasn't possible before
with functions.

Also, JS will now force you to use new when instanciating Fraction class as an
object. You can't call it like a function.

------
DarkWiiPlayer
This antipattern is taken to the extreme with the `DOMParser` [1] class in the
Browser.

The constructor takes no arguments and it has a single instance method that
takes produces a result without mutating any internal state.

What's worse, the method returns a newly created object of another class,
which in my head is an indication that maybe, just MAYBE it should have been a
constructor of said other class instead.

[1] [https://developer.mozilla.org/en-
US/docs/Web/API/DOMParser](https://developer.mozilla.org/en-
US/docs/Web/API/DOMParser)

~~~
chrisoverzero
As it says in the HTML Standard[0]:

>The design of `DOMParser`, as a class that needs to be constructed and then
have its `parseFromString()` method called, is an unfortunate historical
artifact. If we were designing this functionality today it would be a
standalone function.

[0]: [https://html.spec.whatwg.org/multipage/dynamic-markup-
insert...](https://html.spec.whatwg.org/multipage/dynamic-markup-
insertion.html#dom-parsing-and-serialization)

~~~
Tomis02
> The design of `DOMParser` ... is an unfortunate historical artifact

My prediction is that in 20-30 years the same will be said about today's
mainstream OOP.

------
jay_kyburz
I'm fairly new to Unity programming in C# and I was surprised a few months ago
that everything in C# must be in a class. As far as I can tell there is no
such thing as just a function.

Now I have I have a class with some static functions in it.

~~~
wvenable
A static class in C# isn't _really_ a class, it's just a namespace to hold
functions.

~~~
imtringued
Classes in OOP are type dependent namespaces. That's the entire reason why
classes even exist. You can now reuse function and member names inside classes
instead of having to globally qualify them to prevent name collisions. In C
you would do something like class_name_function_name(var, par1, par2). The OOP
counterpart would look like this in Java: ClassName var = new ClassName();
var.functionName(par1, par2).

Now you might be asking if this is really such a big deal. I mean it is pretty
obvious that this can be useful even if you limit yourself to static dispatch
and completely avoid inheritance and polymorphism. The reality is that
functional programming languages like Haskell don't support type dependent
namespaces. When you name a field in a Haskell record then no other record is
allowed to have a field with the same name.

~~~
wvenable
A _static_ class in C# can't have any instance data, instance methods, and
can't be instantiated. It's literally a namespace in everything but name.

~~~
cgrealy
But it does have class scoping rules. You can have public and private static
members.

------
ravenstine
I know that there's arguments for and against classes and functions. In recent
years, I've come to love functions. But they are both tools that have cases of
appropriateness.

However, I think a lot of people are looking at classes the wrong way.

In this thread, I'm hearing that classes should encapsulate functionality.
While this is an aspect of classes, I don't think it's the main use case of a
class.

A class should represent a _type_ of state. That is, if you create an instance
of a state, it should only support the attributes and default properties that
are defined for it. This is mainly for shorthand(there's usually a syntax to
build instances of classes) and for clear documentation. At a lower level, the
availability of a class construct makes it possible for AST parsing to do
interesting things to your OO code, although I don't know how common that is
outside of JavaScript. If JavaScript didn't support class syntax, it'd
probably be more difficult to implement things like decorators, both in terms
of the Babel plugins that support it as well as future native syntax support.
But I digress.

The way I see it, a class should be responsible for holding a mostly fixed set
of attributes, setting defaults for those attributes, and have properties that
compute off other properties.

What doesn't belong in a class are methods that do complex things with other
objects and class instances. If your method does something complicated that
affects outside state, it's time to extract it to a function that takes a
context argument. This makes it easy to test your function without necessarily
having to meet all the requirements of the classes you'd be using with it, and
it makes the code more flexible because you can use it for other contexts
besides a single class. _(I know that you can use inheritance and composition
to kind of do the same thing, but I think this sucks because inheritance
introduces problems and composition just adds more code than just importing a
function where you actually need to use it.)_

More concisely, a class shouldn't be looked at as a module, or a set of
related functions that do complex things, especially when not all of those
functions rely on a state. That's the job of an actual _module_ construct in
the language. Classes have a specific job, which is to build an instance of a
type. If your class has a lot of functionality that's not totally dependent on
the class instance itself, it doesn't belong there in my opinion. Just because
a class can be used like a module doesn't mean that it should.

~~~
bcrosby95
The problem is many popular languages place a heavy emphasis on object
oriented programming. Primarily functional or procedural programming, with the
ability to fallback to OOP when necessary, seems to better align with the
software design sensibilities you lay out here.

You can of course write FP-like code in Java, or OOP-like code in Clojure (or
Golang), but I find I get more done when working in ecosystems I swim with
rather than against.

I think OOP is great. In certain circumstances. Usually rare ones. I don't
think the number of problems it does a good enough job solving warrants
designing a whole language around, because the language will be less than
optimal at solving most other problems.

~~~
ravenstine
That's totally fair. I'm not against OOP per se. OO and FP are both tools
that, if they fit the task, should be used in a way that's not dogmatic. But
obviously you wouldn't try to turn a flat-head screw with a Philips head
screwdriver!

------
yamrzou
In Python you can use class-like functions (source :
[https://charemza.name/blog/posts/python/state/you-might-
not-...](https://charemza.name/blog/posts/python/state/you-might-not-need-a-
class/))

    
    
        def MyClass(...)  # Capitalised to make it clear it's a "constructor"
    
            state_1 = ...
            state_2 = ...
    
            def func_1():
                nonlocal state_1
                ...
    
            def func_2():
                nonlocal state_2
                ...
    
            def func_3():
                ...
    
            return func_1, func_2  # Typically, 1 to 2 funcs
    

I find the approach neat. Here is a concrete example where it is applied :
[https://github.com/uktrade/mobius3/blob/master/mobius3.py](https://github.com/uktrade/mobius3/blob/master/mobius3.py)

------
virvar
I wonder what the author means when he says “ classical polymorphic OOP”. In
my experience, subtyping is the absolute worst part of OOP. More so in an
enterprise setting, where after two decades, you end up with some really
mutated weird ass looking ducks (Animal).

As far as functions vs class go, both are terrible to maintain in the long
run. Both lead you on a never ending path of go-to-definition, because the
documentation is rotten and the tests aren’t right/even there. The only
difference is whether or not you want to search a few large or a metric
fuckton of single responsibility files.

Maybe it’s different if your code bases aren’t crap and you’re a better
developer than me.

------
b123400
While I agree that many things can be simplified into functions, I would like
to point out functional languages don't necessary means you are going to have
cleaner code automatically.

I write Haskell for a company that it seems people here like to experiment
with extensible-effects-interpreters-whatever pattern, that we end up with
some 81 "effects-model-repository-handlers-command-query" packages in a
project, the FactoryCommandQueryEffectContextGeneratorRepositoryHandlers type
is not limited to Java.

The main reason behind this kind of code seems to be "what if you need <insert
a property here>?". As other comments pointed out, using a class over function
can help caching the result, save / restore the state, share the state and so
on, but do we need these properties? The author is talking about a homework,
it's not a piece of code that you run on some very important production
servers, there's no need to cache / save / restore or whatever. What if you
need it in the future? Update the code.

I somehow start to think maybe this kind of mindset is inevitable in a
project's lifetime, until people are confident enough to say "If we need this
need code to be cachable / restorable / whatever, give me time, and I can
update the code."

------
hamandcheese
Is there any real harm though? Free functions pollute the global namespace,
which is something I tend to avoid (at least in Ruby where everything shares
the same namespace).

~~~
cgrealy
Just put the function in a namespace?

~~~
risorect
In terms of ruby, we may be splitting hairs when it comes to namespaces vs
classes. Modules, which are typically used for namespacing, can have state and
extend themselves, making them effectively singleton classes.

~~~
ratww
_> Modules, which are typically used for namespacing, can have state_

But there's nothing about state that makes it specific to classes. Namespaces
in C++ can hold (static) state, for example. So it's actually the other way
around: singleton classes can effectively be namespaces/modules.

------
ChrisMarshallNY
The standard "right tool for the right job" adage comes to mind. If I write
Java or JavaScript, then using objects is fairly important (in fact, I have a
hard time writing good JavaScript, because it is really an "object-based," as
opposed to "object-oriented" language that rewards runtime modification, and
my mind doesn't really work that way).

As was mentioned earlier in the comments, if we are designing code for reuse,
then using a reusable design pattern is important. It doesn't have to be a
class (I program in Swift, which uses structs and enums more often than
classes), but it should be in a form that can easily be extended or derived
from.

I will also do stuff like refactor a bunch of code out in a project that I'm
developing, and create an entirely new project, based on that, so it can be
reused. In that case, I may take a simple, focused tool, and make it a bit
more generic and/or complex, widening its utility (of course, that also means
that I add a bunch of testing that would not have happened, otherwise).

I sometimes think that we get caught up in the tools or dogma; letting them
define us. I say this, having been through exactly that.

------
Someone
_“the count() member function sounds like it should be non-modifying, but in
fact it needs to update member data and thus cannot be const”_

That isn’t correct. Just declare _count_ as _mutable_. See
[https://en.cppreference.com/w/cpp/language/cv](https://en.cppreference.com/w/cpp/language/cv).
Memoization is a valid use case for that feature.

~~~
ketzu
Often it's the motivating example when the feature gets explained.

~~~
hhmc
I would argue that mutexs or other concurrency primitives are the
quintessential example, but caching is the other big one.

------
leekirkhawley
Practically speaking - when I started using OOP in a large structured-code
environment, I was sold on it pretty quickly because it became much easier to
find the functions I was looking for and to understand their contexts. Things
have changed - younger programmers who spend their days writing small
functions that are called by what is effectively a black-box system may not
understand that advantage.

------
zelphirkalt
Add to that, that sometimes you want to pass procedures or functions to others
(I know, crazy, right?). With this "everything must be a class" approach, it
becomes needlessly convoluted to do that. If you don't have static methods,
then you need to create an object first, for the sake of giving that object's
methods to another procedure or function. It makes using the functionality
more cumbersome.

For usage I'd say:

Use functions, whenever you can get away with only using functions, as long as
their arguments stay reasonably few. Only use a class or struct or other
"putting-together/wrapping concept", if you have to. For example, if your
functions or procedures would have 10 configuration arguments, it is probably
a good idea to wrap those in a struct and give them as 1 argument only.

Where I see OOP mostly is with GUI frameworks. I know functional GUI
frameworks do exist, for example [https://docs.racket-
lang.org/gui/index.html](https://docs.racket-lang.org/gui/index.html) (well
functionally using objects at least), but in many cases the framework will be
written in an OOP style and one needs to adapt to that, to have a good time.

Where I don't see a reason for OOP is for pure calculation things. For example
recently I wrote a simulator for calculating probabilities in the board game
risk. No need to do any OOP there at all. It's all functions or procedures,
except for 1 struct, which wraps the rules of the game and is passed mostly to
all calculation procedures, so that they can take from that struct whatever
they need for calculation.

Many things inside a project will be pure calculation things, where this kind
of approach is applicable.

~~~
mpweiher
> pass procedures or functions to others

"Object interfaces are essentially higher-order types, in the same sense that
passing functions as values is higher-order. Any time an object is passed as a
value, or returned as a value, the object-oriented program is passing
functions as values and returning functions as values. The fact that the
functions are collected into records and called methods is irrelevant. As a
result, the typical object-oriented program makes far more use of higher-order
values than many func- tional programs."

William Cook, _On Understanding Data Abstraction, Revisited_

[https://www.cs.utexas.edu/~wcook/Drafts/2009/essay.pdf](https://www.cs.utexas.edu/~wcook/Drafts/2009/essay.pdf)

~~~
zelphirkalt
Only that you need to artificially create all those things, even if they would
not be necessary, while, when you work with simple functions, you just do
them.

Here is what I mean:

For example you would need to create an interface for some classes (or
interface for other concept your language offers), which tells you in another
place in the code, that an object implementing the interface will definitely
have that one method you need. You need to create a class or whatever your
programming language offers, to implement the interface and give that as
argument.

There is a lot of boilerplate in this. It also is a question of explicit vs
implicit at times. I usually like having things explicit instead of hidden or
implicit and encoding knowledge in types is often great. However, if I am
forced to create an interface, to be able to create a class implementing the
interface, to be able to create an instance of that class, to be able to give
that as an argument ... I prefer just being able to pass a procedure or
function instead. At some point enough is enough and it does not make
understanding the code easier, when I have to check in 3 places.

------
signa11
general rule of thumb here is: to use classes if there are some invariants
that can be checked/maintained. or, in other words, invariants exist to
justify the existence of classes. ofcourse, the constructor establishes the
invariants, and everyone else maintains it.

if you have to do things like 'get_name(...)' and 'set_name(...)' then it is
kind of silly (imho). just stick to more canonical means.

------
codesnik
Maybe it's not that relevant here, but in most ruby on rails projects nowadays
there're a lot of "service objects" which are intended to be instantiated with
all the needed inputs, and to have just one public method "call" without
arguments, meant to be called just once.

So, from all the perspectives, they are just functions. But they're not
defined as such usually, only because it'd be slightly awkward in ruby for
those functions(methods, actually) to have private functions inside. Because
there's no import mechanism in ruby, only mixins, which "imports" all the
private methods in the module as well, polluting namespace of your class.

As a side measure, those service classes usually have a class method "call",
which just passes all the args to the new instance of the class and calls the
"call" on it, so you can later just do "ServiceObjectClass.call(args)" or even
weirdly looking "ServiceObjectClass.(args)"

It looks very awkward IMHO, but I haven't seen better alternatives yet.

------
commandlinefan
Although he's correct at a high level, I'd say this should be handled more
like the advice given to programmers on optimization:

Optimization for beginners: Don't optimize

Optimization for experts: Don't optimize - yet.

What I've observed, over and over (for the past 20 years of Java development)
is that beginning programmers don't really see much point in object-oriented
design, and default to static functions (Java's equivalent of "free
functions"), but they also end up needing some shared state... so they make
the shared state static, too (Java's equivalent of a global variable). Every
"enterprise" Java project I've worked on since about 2002 has been "designed"
this way: almost all the data is public static (e.g. the "singleton"
abomination) so you can't run any of it without running all of it.

On the other hand, if developers just defaulted to designing objects even if
they don't really get why, they'll end up with a composable, testable system
by accident.

~~~
jbay808
Composable maybe, but testable, not necessarily.

If the function doesn't read or modify a global state, it can be much easier
to test in isolation than a class, which might e.g. throw an exception in the
constructor causing your test to fail before you even get to run your method.

If it does touch a global state, it's probably instead better as part of a
class.

------
fallingfrog
I had to look up what memoization is- guess I’m getting rusty? But it turns
out it’s just a new word for an old concept (caching).

I’ve always done it this way:

double calculation(double arg) {

    
    
      thread_local bool prev_arg_valid = false;
      thread_local double prev_arg;
      thread_local double prev_result;
    
      double result;
      if(prev_arg_valid && prev_arg == arg)
        result = prev_result;
      else {
        //do the calculation 
        result = ...;
        prev_arg = arg;
        prev_result = result;
        prev_arg_valid = true;
      }
      return result;

}

Doing it this way is something you would only ever do if you knew beforehand
the user would be asking for the same value over and over, but it’s clean and
invisible to the calling code and doesn’t break your assumptions about what
the function is going to return.

The memoization pattern is valid too of course, I just find it interesting
I’ve always done the same thing when advantageous but in a different way.

~~~
hhmc
> I had to look up what memoization is- guess I’m getting rusty? But it turns
> out it’s just a new word for an old concept (caching).

The term memoization was coined in 1968 and quite possibly predates the term
cache (with respect to computing).

~~~
fallingfrog
Interesting!

I still maintain though that memoization is a special case of caching with
n=1. :)

------
allenu
I try to avoid classes as much as possible these days. I do mac/iOS app
development, so I can't avoid it since I need them for defining UI components.
However, for non-UI code, I tend to use classes to compose separate pieces of
state at very high level. The class acts as the "state machine" that updates
these separate pieces of data that can only transform in a few explicit ways.
Functions define transformations of these pieces state, so they're easy to
comprehend and test. I didn't come up with it[1] and it's worked out really
well for me.

[1]
[https://www.destroyallsoftware.com/screencasts/catalog/funct...](https://www.destroyallsoftware.com/screencasts/catalog/functional-
core-imperative-shell)

------
mrep
Interesting. I usually default to writing functions over classes for the
simple reason that it makes testing much simpler as you just need to test
inputs versus outputs to a function.

With classes, you sometimes have to manage and test the state of it. For a
database example of something I had to design a while back, it required me to
manage 4 different state transitions each one depending on the state of the
previous one. Thus, I ended up with this long test case running through each
transition which was not ideal. Granted, I could have split the state
transitions into multiple tests by setting up the internal state manually in
for each test/transition, but I prefer not to fiddle with internal state in
tests.

------
majormajor
Over time in a project, "flexibility" often starts to become synonymous with
bug-prone, and poor organization of classes/functions/data is another big
killer. Three functions in different places doing almost the same thing but
with unique quirks, and you being fairly new to the codebase don't know that
any of them exist, so you write a fourth.

Neither OO nor functional approaches solve this inherently, but the tools of
OO provide some nice options. Just think about _why_ you'd be using them for
each case.

------
lifeisstillgood
I have to admit i do write far far fewer classes these days - it seems to be
my last go to approach usually once I have understood the domain and needs
quite well (i tend to do a lot of exploratory work)

------
sebsito
Depending on the language it could be tricky. For example in Ruby everything
literally is an object. There are no standalone functions. Not having a class
simply makes it hard to use. I can't autoload methods. I could group them in a
module but that's not the point. So while coding in Ruby I'm planning to stay
with small classes. I treat class as a smallest testable entity in Ruby.

On the contrary, in Python, a function is a first class citizen which can be
selectively imported and easily tested.

~~~
jordinl
> For example in Ruby everything literally is an object. There are no
> standalone functions. Not having a class simply makes it hard to use

Nothing prevents you from writing one function in a file, requiring that file
and calling that function. Not sure what's so hard about it.

~~~
ClikeX
If you write Ruby from scratch, sure. If you work in Rails you have to work
around everything being autoloaded on boot.

Best bet is to just build modules to namespace your functions.

------
jerome-jh
To me the original sin of the example is not to perform the calculation in the
constructor. The third version, the one with the "int count() const" method
and without the h and w members, is perfectly valid and (correct me if I'm
wrong) thread-safe. It is a bit longer than the 'functional' implementation
but does handle memoization. This is basically the C++ way to make a
'closure'.

The point is certainly valid, but the example could have been more carefully
chosen.

------
dep_b
So one of the problems with that function is that it's global. The advantage
of having a single class that either has a hidden constructor and only a
static function or needs to be initialized but only offers the stateless
function is that you know what _concept_ the function is related to.

Sometimes I have an entirely stateless class that has a cluster of 10
functions, both private and public just to have that particular functionality
grouped somewhere.

So class-as-a-namespace if you wish.

~~~
ketzu
If you use c++ (as the example in the article) you can just use explicit
namespaces at least.

~~~
dep_b
True, I'm working with Swift which doesn't have that concept. But you can't
have collisions between similarly named functions that are in the main or a
different namespace? For example when somebody defines it's own version of
'print'?

~~~
ketzu
You can have name collisions of functions with the same name, same namespace
and same signature. But you can have nested namespaces, so namespaces are
still the answer in c++.

In languages where those are not available or where you are forced to use
classes anyways (e.g., java) I see no problem with classes-as-a-namespace, but
especially in java there is no point in discussing "classes that should be
free functions", too, as those don't exist.

------
strokirk
As a Python programmer, I've found that since I started using `mypy` I've
written a lot more classes - I generally like using multiple small functions,
but classes have two big benefits for me:

1\. I don't have to repeat / import type signatures as much

2\. I can collapse a bunch of related functions more easily in my editor.

So functional programming (in python at least!) has a few things to solve
regarding ergonomics for me to return to it.

------
default-kramer
Whether or not a class can be reduced to an int is something to consider, but
should not automatically proscribe the use of a class. What is way more
important is to carefully consider the API that you provide to calling code.
Once other code starts depending on your decision, it becomes harder (or even
impossible) to change. So you might decide to use a class for this reason,
even if it can be reduced to an int.

------
logicallee
Here's my view: the real world doesn't have any free functions. Say a plate
breaks in your kitchen. What happened? Obviously some kind of an object
derived from a circle and and and some sort of factory? And, uh. Josh you
mentioned broken plates. So, as far as a broken plate, I think we can address
this by adding some methods for how many pieces the plate is currently in and
which broken piece we're talking about then we can specify each broken piece
as a simple polygon from the origin of the virtual object, that being the
theoretical still-intact plate as manufactured. We can also specify each plate
in terms of the deviations from the platonic ideal plate as specified in the
CAD designs. So let's change all the signatures to refer to EITHER 1 whole
piece, being the whole plate, or the first broken piece, second broken piece
and so on, which can all end up in different places in the world. Ultimately
this will cover just about everything except if someone makes an artisanal
plate by gluing together previously broken plates, where they're not from the
same plate. That will make garbage collection difficult as the artisanal
factory might leak memory if it doesn't use every piece. The solution is to
simply do manual garbage collection where we replace each derived plate that
is made up of broken plates, with a new plate that is pre-broken, and we just
have to set what pieces it is made of, and then the original pieces can be
freed. So actually the base case does need to know whether it is considered
"whole" despite being made up of broken pieces, and then it can have virtual
pieces that comprise it but know that they have to move and be together as
one. This basically captures the plate abstraction pretty well, as long as you
remember that we decided that for the dinner function we decided to set a
plate, rather than set a table, so you just do the glass.set() the fork.set()
and plate.set() so on, and then you can check that it's set, whereas the table
they're set on might have unrelated items such as a candle, that isn't really
considered set or not.

Javascript: how about we all just use _paper plates_. do whatever you want
with them.

Programmer: also works! Obviously we'll automatically update the DOM tree
whenever the server thinks anything's changed, but that's just common sense.

Javascript: ...also feel free to pour soda in them, paper plates are cups too.

Typescript: hold it! Not so fast!

------
achn
While not bad advice, I feel that this post does a very poor job of explaining
when and why to apply it. The original class, while certainly inefficient,
exposed fields and memoizes results, the final function does not. He’s
combining two pieces of advice - don’t over complicate your design, and don’t
create classes for a single use function - but neither one is demonstrated all
that poignantly here.

------
asiachick
What is it like to auto complete in a functional language?

In OO if I have an instance of a file

    
    
        f = new File(...)
    

Then in my editor I type `f.` and I get Close/Read/Write as possible
completions.

In some functional language I have a `f` an instance of a File, what do I
press to get all the list of common things I can do with with a file?

Is that better or worse or what?

~~~
ahmedalsudani
That kind of intelligent auto-complete does not require OO. It requires static
types.

~~~
asiachick
you didn't answer my question. I already said I had a type, the type is
`File`. Instance of that is `f`. What do I type to see ways to use `f`?

~~~
ahmedalsudani
¯\\_(ツ)_/¯ depends on your code editor and your tooling.

In strongly typed functional languages (Haskell, MLs), you don’t even use OO
syntax. Instead you pass your objects into functions. E.g. in Haskell:

    
    
        writeToFile file data
    

So the topic of the question does not really apply to functional languages.
Personally I gave up on auto-complete with Haskell a long time ago, and it
hasn’t been a difficult adjustment. Writing Haskell is still way more pleasant
than C++.

------
TOGoS
In other words, don't use instance variables for argument passing:
[http://www.nuke24.net/docs/2012/InstanceVariableMisuse.html](http://www.nuke24.net/docs/2012/InstanceVariableMisuse.html)

------
robomartin
We recently ran into a library for motor control that was an absolutely
impenetrable set of classes. After navigating though the code we distilled the
entire library to one import (the same one they were importing) and a single
line of code. Yup.

Not sure why people do this.

------
quotemstr
A classic on this subject: [https://steve-
yegge.blogspot.com/2006/03/execution-in-kingdo...](https://steve-
yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html)

------
beefbroccoli
In typescript I will for example make a class called 'Tools' with static 'free
form' functions and no constructor. I do it just because it's easy to
organize, import and call the functions. Is this bad?

~~~
glassounds
Not saying this is "bad", but why not use a module for that?

~~~
beefbroccoli
Not sure. Maybe time to convert some classes to modules.

------
matheusmoreira
Related: [http://steve-yegge.blogspot.com/2006/03/execution-in-
kingdom...](http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-
nouns.html)

------
skocznymroczny
Why make it a class even, it doesn't hold any state. Also I'd do it like:

DominoTilingCounter tc; std::cout << tc.count(4, 7) << '\n';

~~~
Someone
Then, _count_ could be a static method, and you wouldn’t need to construct the
_DominoTilingCounter_ , yielding

    
    
      std::cout << DominoTilingCounter.count(4, 7) << '\n';
    

That’s close to a free function, using the class only for grouping (say of
private helper functions, or for memoization data structures)

------
29athrowaway
His cppcon talks are very insightful and interesting.

------
jariel
Well, there is one subtle, possible advantage and that is literally just the
logical grouping of code, not really encapsulation in the technical sense.

Second, if you have a handful of functions like this, even remotely related
... they make a nice 'Library' and frankly there is nothing wrong with that.

As I see it, I don't think it's an anti-pattern depending on how it's used.

In some other instances, first-class functions or lambdas may be just as
appropriate.

------
jjgreen
-er classes! (shakes fists at the sky)

------
plerpin
Put a FooHelper on it!

------
kleiba
<deleted>

~~~
evozer
There is, it's called static. Just hide it in a .cpp file and expose the real
function in a header file.

------
yen223
For anyone who wants to post their opinions on whether OOP is good or bad, may
I suggest briefly explaining what you consider to be "object-oriented
programming"?

I've seen in a lot of threads like these, people often end up talking past
each other, because one person's idea of what an "object" turned out to be
different from someone else's.

~~~
hivacruz
I would really love to hear it too. I made some OOP on various programming
languages (mainly PHP/Ruby) and where sometimes this is obvious that using OOP
is the solution (interfaces, abstract classes etc), sometimes I feel it is
completely overkill and I don't even know why I'm using it.

For example recently, as an exercise, I made a little scraper in Python to
extract movies data from a website [1]. This could have been done in
procedural programming directly (actually the first iteration was like that),
but I rewrote the whole thing as a class and I still don't know why. I think
it's completely overkill.

If I was doing a scraper for multiple websites related to movies, that would
make sense to use classes as they share the same goal and I could make good
use of heritage/interfaces/abstract classes. But here, it's not. I read again
and again about when to use OOP or not but I still struggle to find most of
the times the good decision. It's like I doing it just because it looks
"better".

[1]: [https://github.com/kinoute/scraper-
allocine](https://github.com/kinoute/scraper-allocine)

~~~
disgruntledphd2
Traditionally, I don't really like classes, as I started with a mostly
functional language (R).

However, when writing Python (for a poker simulator), I came to the conclusion
that objects can actually be really useful for the following reasons: 1)
managing state: this is the big one, if you need data with your functions, a
class is an obvious way of doing it.

2) documentation: a class with methods is a higher-level structure than a
bunch of functions and you are more likely not to forget the existence of a
method on a class relative to a function somewhere in your code base (this
second idea shamelessly stolen from Martin Fowler).

------
skrebbel
Patterns and antipatterns are important, but naming is even more important.
Calling "using a class when a function would suffice" "the OO antipattern"
isn't going to help anybody.

I wish we wouldn't let today's tendency towards clickbait titles extend into
engineering terminology.

~~~
dang
I agree - it's baity and misleading, and so should be changed
([https://news.ycombinator.com/newsguidelines.html](https://news.ycombinator.com/newsguidelines.html)).
I've replaced it with the article's phrase for what it's really about.

~~~
Tomte
In this case I don't find it terribly misleading.

One thing that occurs to me regularly now is that I click on a submission
that's new to me – only to find that I've already seen it under a different
title.

I agree with the policy of renaming submissions, but I wish you'd adjust your
trigger level slightly towards less changes.

------
throwaway_pdp09
tl;dr Use the right tools appropriately.

------
kerng
This is why I like languages where you just can't have a free function!

I'm arguing the opposite, free functions are an annoying and confusing anti-
pattern when dealing with OO languages that allow them.

C++ is often just a maze of mostly write only code.

~~~
CyberDildonics
You aren't arguing anything, you are only stating a very shaky premise.

~~~
kerng
Not any less meaningful then your comment. You aren't arguing anything, you
are only stating a very shaky premise.

~~~
CyberDildonics
What I said wasn't a premise, it was pointing out that you didn't back up what
you were saying with any substance, which can be checked by reading your
comment.

