Skip to content

Comment on Classes often aren't the simplest tool for the job

Comments

This is a pretty low-quality post. Of course you can pick on Java for shoving every feature into a class. That says nothing about classes in general.

Printing: Java is verbose because it requires a class. Most languages with classes don't.

The namespacing example in JS is weird because JS has namespaces as pointed out already, but again this case is only a problem in Java where you might use a class just cause everything has to be a class.

Functions: same thing.

Objects: not all languages have a separation between objects and class instances, those that do don't require you to use classes. :shrug:

Object with shared behavior: yes, this is where classes are useful because this is practically the definition of classes.

So the bottom line is use classes for the things they were designed for. Who knew?

I believe that classes, inheritance and OOP as the leading paradigm for two decades were the worst thing to happen to programming after NULL.

You don’t need classes at all, classes are simply a mix of several concepts that are best kept separate: Currying, namespacing, data structs and closures with a bit of syntactic sugar on top. Worst cake ever.

I code in C, Rust, Typescript and Python, and didn’t code any class in the past 5 years, because classes are never the best option. The mix of data and behavior in particular is horrendous, and made much worse by inheritance (now you don’t know which behavior is used without exploring layers of abstractions).

Inheritance was a solution to a problem caused by classes and would never have existed, had other paradigms been dominant. It caused headaches and bugs for generations of programmers (Think of the ORM problems, etc.).

Just code simple, directly serializable data structures, and functions and you’ll be much happier.

I think we have two failed concepts in classes: inheritance and encapsulation done wrong.

I don't need to explain why making excessive use of inheritance is wrong. Encapsulation is done wrong because you end up with lots of hidden state and a very complicated dependency graph.

Indeed, one of the pillars of OOP is encapsulation. (= encapsulation of state = hiding state = every object can maybe be stateful, but you can't know from the outside).

Meanwhile, one of the pillars of modern programming techniques (FP, React, and others) is to make state very explicit and separated from the rest (Think State Monad, react's useState, etc), because statefulness is now correctly identified as being a major source of problems.

So OOP took us completely in the wrong direction regarding state management, for 2 decades... Understanding in 2014 that was liberating for me.

Classes turn out to conflate two or three different, unrelated concepts. Better languages split those things out so that you can use them separately. (Believe it or not, Java actually advanced best practices in this area: in C++ interfaces were just a "pattern", and many C++ fans were sceptical of the value of making a language-level distinction between interfaces and classes).

When we say "A extends B" we mean a) an instance of A composes in an instance of B b) A implements the interface that B implicitly expresses c) this implementation, by default, delegates to the instance of A from a). Sometimes you really do want to do all three of those things. But often you want to do some subset of them instead. So the language should give you ways to do parts of that without doing all of that, and then "extends" should be at most lightweight syntax sugar over that.

Sometimes you really do want to do all three of those things. But often you want to do some subset of them instead. So the language should give you ways to do parts of that without doing all of that, and then "extends" should be at most lightweight syntax sugar over that.

Sounds like you’re describing something like C.

Leverage individual header files to implement a specific behavior rather than features concretized in a spec we don’t always want.

Making everything a class, monoid, etc. feels like the programmer equivalent of making 9 Star Wars movies where each is comprised of lightsaber fights from open to close. That’s a cool still image but … really 9 movies huh?

Sounds like you’re describing something like C.

Not at all. C has essentially no support for interfaces or polymorphism, yet alone delegation - even its data structure support is wonky (no real sum types). If C++ classes are "all you have is a hammer", C doesn't even give you the hammer, so you end up bashing screws in with a rock.

What I advocate is something like Rust, where you have both structs and traits as distinct concepts that serve separate purposes.

When we say "A extends B" we mean a) an instance of A composes in an instance of B b) A implements the interface that B implicitly expresses c) this implementation, by default, delegates to the instance of A from a).

This is probably the best summary of why traditional OOP is often suboptimal I've read. Clearly OOP solves (or solved) a problem that people were having, it's just that it tries to solve too many problems with the same system.

It's also worth noting that fans of "composition over inheritance" usually don't notice or mention that if you go with pure composition, you lose out on point (c) in most programming languages. Very few languages have a built-in mechanism by which a set of operations on type A can be automatically delegated to one of its elements, without at a minimum, listing all the operations to be delegated. So at least at present, traditional inheritance still provides an operation which is not available with composition.

We're starting to see languages have support for delegation - Kotlin has it and Rust was at least talking about it. I agree it's still pretty rare though.

I am confused. Is there something you disagree with? Or are you saying that the points are obvious and thus not worth making?

(FWIW, I wasn't too happy with the writing quality when I published it. I think it is fine, but not great. I spent some time trying to improve it, but the words weren't quite coming to me. I considered throwing the post away, but decided not to. I think that the central point (as in DH6 from How to Disagree[1]) is a good one, and even though I wasn't able to express it as clearly as I'd like, it is still worth publishing as more of a conversation starter type of post, not as the official/reference post on the topic. In retrospect it would have been good express this in an epistemic status section[2].)

[1] http://paulgraham.com/disagree.html [2] https://www.lesswrong.com/posts/Hrm59GdN2yDPWbtrd/feature-id...

Sorry, I didn't realize the author submitted the piece, so I apologize if my comment came across as harsh.

I think the issue for me is that until you get to objects you're listing things that classes are not designed for and in most languages wouldn't be used for anyway.

Each point sounds like:

* Don't use classes for functions, use functions for functions. * Don't use classes for top-level statements, use statements for top-level statements.

Which while true, was only ever a thing to begin with in Java which made a pretty weird design decision and forces your hand. So the whole piece reads as a criticism of Java mislabeled as a criticism of classes in general. Yes, Java's weird, but that's Java's problem, not OOP's.

The park about this theme of OOP criticism that irks me is that there's a lot of irrational class hate out there and writing that classes aren't the best option for things which classes aren't intended for just adds to it. Yes, classes are not replacements for all functions, they never were.

No worries. And I gotcha, your comments make more sense to me now.

What I was going for in giving the examples was just to build up from the most simple to stuff that is more complex. Like: "Here they aren't the simplest tool for the job. What about here? No. What about here? No. What about here? Not quite. What about here. Yes!"

It felt like a solid way of making the point. I didn't mean to imply that I think it is common for people to use classes for eg. print statements. I tried to protect against this interpretation in the the last section:

I want to keep the scope of this post narrow. I don't want to start getting into the weeds about the pros and cons of using classes vs using alternatives to classes. My goal here is just to point out that simpler alternatives often do exist. Hopefully that perspective can help better inform the decisions you make when writing code, and the opinions you form about programming languages.

---

The park about this theme of OOP criticism that irks me is that there's a lot of irrational class hate out there and writing that classes aren't the best option for things which classes aren't intended for just adds to it. Yes, classes are not replacements for all functions, they never were.

I hear ya. I think that like most things, there are people at the extremes of both sides of the spectrum saying stupid stuff. On on end you've got the people who love classes and think eg. that it's ok to not have first class functions. And then on the other end you've got people who hate classes and think eg. that there is basically no good use case for them ever (I came across this a decent amount actually as I was googling around before writing this post). It sounds like you and I are both somewhere in the middle and agree about the broad strokes.

As an example, of the "love classes" extreme, there is a particular pattern I've been forced to use before that I hate. In a ruby codebase, instead of creating a utility function, we have to create a class with an instance method named `call`, instantiate the class with the arguments you intend to use for the method, and then call the method with no arguments. So like instead of `getFullName(first, last)` you'd have to do `NameService.new(first, last).call`.

But many other languages are like this. AFAIK, C# is the same - there are no first class functions allowed, only classes.

In practice, the difference is pretty small in C#. For almost all purposes classes act like a namespace (System.Math), and can be imported like one. Former 6 will add even more syntactic sugar, so that methods can be defined outside classes.

You can import static methods in Java too. Works the same.

In C# I either use classes as namespaces or use them as data structures. Using and static keywords help a lot.

I write lots of C# but I try to use as little of OOP as possible.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.