Why OO Sucks
sics.se
sics.se
* Data structure and functions should not be bound together
In some cases encapsulation works really well. For example GUI elements. It is less true for "process-oriented" components. But the arguments he makes don't seem very well thought out.
* Everything has to be an object
I actually agree here. To my mind, the biggest problem with OOPS is that is slowly devours all other concepts until we are left trying to force-fit all problems into the OOPS mould.
* In an OOPL data type definitions are spread out all over the place
This just ignorance.
* Objects have private state
This is also a good point. Having state actually tends to being a "bad" thing to do. Of course, the entire purpose of the object is to capture some invariant, but an object doesn't make that clear. Many objects contain instance variables that should actually be parameters.
* Why is OOPS popular?
Because it makes it easier and safer to get programmers working together as a group. The vocabulary of available objects is a simple and somewhat elegant way of communicating.
(EDIT: Corrected layout.)
In a typical OO language all the code belongs in classes, and there is no way of telling which classes define the essential data types and which are mostly placeholders for code. There is no way to tell at a glance what are the important data structures. Just look at any open source Java project.
I use python and I find myself using more functions and fewer and thinner classes recently. In particular, if some state is transient, I find it is much better to keep it in local variables of the more abstract functions than obscure it as object state.
EDIT: I'm using the word "ubiquitous" because that's what was used in the article.
The 'basic' package is certainly not common practice.
Otherwise, I consider the blur between code and data a good thing to have.
You can assign them to a variable, and you can write out a function literal. What more does it take to be a "first-class citizen"?
...for a given function you can't request / modify it's syntax tree
And what language does let you do that? Macro systems like let you do it at compile-time, but when you use the phrase "request an AST", it sounds like you're doing it at runtime...
Io does.
http://hackety.org/2008/01/05/ioHasAVeryCleanMirror.html
But functions are first class objects in Ruby (with some quirks regarding the difference between a proc and a bloc).
And even in C# 3.0, when passing lambda expressions you can request a syntax tree instead of a method reference.
F#, like OCaml, supports proper quotation and allows you to create executable code, or ASTs, or both using metadata attributes that the compiler is aware of.
Making OO the ultimate dogfood.
Whenever I encounter any kind of new "thinking", the first question I ask is, "Who is it good for?"
Ultimately, OO was made for us, not our customers. True, anything that helps us helps our customers, but I'd prefer to ask a more basic question...
If we have so many programmers that we need new technologies to work better together, are we doing something fundamentally too complex to begin with? Most of us aren't working on the space shuttle. We're doing relatively simple bread and butter apps moving data through logic. Before we surrender to OO, maybe we should take a closer look at why we need "easier and safer" ways to work together. For most of us, a simpler approach than OO often makes more sense. I suspect that's OP's biggest point.
What happened next was that an idea that worked well in a few contexts (discrete simulation and graphics/GUIs) was expanded to almost all kinds of programming where it didn't work as well. That's because a lot of the world-to-be-modeled is more verb than noun. That in turn means that functions will make better models than objects.
I now think that Lisp-class functional languages (using, say, Clojure) are better for most programs. The reason, as pg has pointed out, is succinctness. The problem with OOP is that it's hard for it to be succinct because new data types are constantly being created and new methods added to them, all of which requires extra code.
All programs are programs about a particular domain. Programs become succinct when the language being used can be sculpted to the domain at hand. That's best done in languages with macros and a simple (e.g., s-expression) syntax.
One last point. I've looked for many years for the One True Programming Paradigm. I no longer believe that any such thing exists. Different classes of problems simply require different approaches. What I've come home to is using a language that allows easy creation of DSLs and controls side-effects, while still allowing them. A wet clay language that allows experimentation and sculpting to the domain at hand. Lisp-class languages do this best, with Clojure at the top of the heap in my opinion.
I still think that OOP is best for GUIs, where state is inherent in the domain and inheritance can pay off big.
Additionally, he's run up against the pitfall with OO - it actually lacks a definition. It's got lots of dimensions and facets, and each implementation interprets these in it's own way... e.g You could argue that SmallTalk's message passing metaphor was the precursor to Erlang's model.
>When I was first introduced to the idea of OOP I was skeptical but didn't know why - it just felt "wrong".
It's more a personal rant than a well thought-out article.
I think OO is well-defined.
I would define it thus: an object is something with state and behaviour. An OOPL is a PL with facilities for handling objects. These facilities must include (i) specialisation -- either through subclasses or prototypes -- and (ii) method invocation determined at run time, so that for example:
someShape.drawOn(aCanvas);
their are several drawOn() methods and the one that is executed is determined at run time by the class (or equivalent) of someShape and possibly also by the class of aCanvas.The OO paradigm is a parcel of a lot of different things. When it was released, Java hit a nice sweet spot of these with the developer community.
There are plenty of OOPLs where this is not the case. For example C++ or Python.
> Why is OOPS popular? Because it makes it easier and safer to get programmers working together as a group.
That's true. But the main reason why OOPL is popular is because it makes programming more productive, whether there's one programmer writing the code or lots of them.
It is now, but it wasn't always. For example, you didn't used to be able to create a subclass of dict or int. And you can certainly code in Python just using built in data types and functions, not making use of any OO features.
To see how C++ has moved towards objects-for-everything, I have just one word - boost!
RE your second point - I don't know of any studies that show OOPS makes programmers more productive. As a matter of fact, some anecdotal evidence (cough pg cough), points to the opposite!
Yikes guilty as charge here as I currently have a unhealthy fascination for the use of instance variables if not all in most of my objects. Thanks for the heads up.
The impressions I get are that Armstrong likes to stay close to a core set of raw data structures; he's more about the flows than the stocks; he finds delegation and specialization more frustrating than helpful; and he would rather build a combustion engine than an office building.
(He's probably an ISTP personality type at work but has put on his ISTJ hat for the purpose of writing this article.)
I agree with him that having the most important data structures of the program scattered all over the place makes it needlessly difficult to understand, but I think many of the major problems with OO actually come from overusing inheritance. Compounding this, OO design can be very brittle: It works in theory, but in practice, it only takes one person who doesn't really understand OO (or one person having a dumb day) to screw up the whole design in subtle ways, and there will always be someone on a team who isn't quite up to speed. Worse still, when OO breaks down, it tends to gradually drown the project in busywork ("...if we just move this from this class to this class and rewrite a bunch of delegation code...") rather than just being an obvious dead-end.
It seems like focusing on interfaces rather than a hierarchy of classes cuts out many of the problems with inheritance. Something like ML functors or Haskell typeclasses seem like a good fit, as well. (I haven't tested those on any especially perversely organized projects yet, though. The measure of a technique is whether it still works on hairy stuff, not whether it works well for idealized examples.)
Also, while this article by Clay Shirky (http://www.shirky.com/writings/ontology_overrated.html) is more about categorizing information, it demonstrates very nicely why trying to pigeonhole everything into a hierarchy of classes doesn't always work in practice. (I posted it to the front page, since it hasn't been yet...)
Nowadays, I sort of just use classes to scope and namespace. I keep state in classes when it makes sense.
To counter the point about state, Erlang programs do have state. They're just contained locally within a function. Since Erlang has tail recursion, you just call the same function recursively and pass the state variable you want to keep at the very end of the function for the lifetime of the program. Because it's tail recursion, there's no stack overflow. Also, Erlang threads can keep 'state' inside of the Mnesia database that comes with Erlang.
State is only evil when it's shared. Because state is contained locally in a function, a single Erlang thread can do whatever it wants, whenever it wants with those variables. Threads communicate through message passing. Therefore, no need for locks, mutexes or semaphores. Along with the low overhead of thread creation (~300 bytes) they're part of the reason why Erlang has great concurrency properties.
This is not true of "OO", it's just true of OO in languages like C++ and Java. These languages got OO horribly, horribly wrong; so if you use them and dislike OO, it's possible that you dislike the language -- not OO.
If you use CLOS, you'll see that functions are functions and data are data. CLOS just gives you some tools for calling different functions depending on the type of the data and for specializing subtypes. This is OO done right, and it is not what the author of this article is referring to.
Objection 2 - Everything has to be an object.
Everything has to have a type, yeah. But the line between "built-in type" and "object" should be blurry, and in CLOS it is. Again, if you are hating OO because of Java's implementation quirks...
Objection 3 - In an OOPL data type definitions are spread out all over the place.
I am not sure what the problem is here. Unless your code is all in one file, this is going to be a problem regardless of the programming "paradigm" you choose.
When writing Lisp, I often put all my class and method definitions for one chunk of the app in the same file. I think this is what the author wants to do, and it is do-able and commonly done in CLOS. In Java, yeah, you have to put each class in its own file. That is irritating, but the problem is not OO.
Objection 4 - Objects have private state.
This is true. I prefer to write OO code that maps one object to another, rather than mutating an object's internals. The sad reality is that this approach is slower and uses more memory than the mutation approach, but I think it's easier to reason about and maintain. Computers are fast, anyway.
So while it's possible to misuse OO, whatever that means for your particular tastes, it's also possible to not misuse it. If you hate OO, it is possible that you are doing it wrong.
For Objection 2, well as you said, the distinction should be blurry. I really don't understand what his objection to Time being an object is. What Erlang is doing with deftype is basically creating a new "constructor" for an object that includes bounds-checking.
The problem in Objection 3 is that he's dealing with file-based languages (and so are you). If you use any Smalltalk, you'll find a nifty browser that organizes things by package name, and there are even categories for methods.
I wish people would stop posting ill-informed rants on Hacker News.
wrt objection 1, I think Smalltalk goes against the model he wants. Methods are "messages", and eventually one object has to handle the "message". CLOS is slightly different -- the message is sent to the MOP, which does whatever it wants to to handle the message. Usually, it finds an applicable method and calls it, but you can override that if you want. The key distinction is that the class never cares about messages -- the MOP does.
I agree with you on point two. I should clarify that objects are not just "opaque structures with slots and metaclasses" -- they can be anything you want. I have a Moose metainstance class that stores integer fields as bit offsets in an integer (or bit vector, if you prefer). So your data looks like a number, except it has some type information (and a metaclass) associated with it. I really can't think of any way to construe that as a bad thing; if you want to use OO to manipulate the data, you can. If you want to treat it like a regular integer, you can. Everyone's happy.
Your third point is very good. Smalltalk completely removes the concept of files, and therefore you can group things together however you want. This is really orthogonal to OO; you could group functions inside a browser too. But OO helps keep things organized, which is always good.
Anyway... I agree with you. It really bugs me that there is so much uneducated hate for OO out there.
Everyone should be required to use CLOS and Smalltalk before they rant about how much they hate OO. :)
Use Objective-C if you want to program the Mac or iPhone in the near future. It largely copies the SmallTalk model, but grafts it on to C, which means that you can still do all the non-OO things that C can do. This engineering compromise seems to have turned out to be a pretty good design choice, but perhaps not as aesthetically pleasing as the "pure" SmallTalk design.
I know when I was first starting out, I wasn't capable of writing an elegant line of code. I barely am now, but at least I can recognize it when I see it.
I'm possibly damaged from a few years of OOP but how do you separate a data structure from its functions? e.g. How can a B-tree mean anything separate from its search/insert/delete operations?
For instance most examples on the net just assume keys to be long numbers but that is obviously insufficient for an actual application. Keys could be 20-bit hashes, strings, floats, doubles etc. The same applies to a lesser degree to the offsets (at leaf node level) and any embedded values.
Plus it is interesting that you view a B-Tree as an object - when I think about it, I categorise it as an "algorithm" operating on various data objects.
I'm confused, you store values in the tree but in itself it is not a data structure?
Plus it is interesting that you view a B-Tree as an object - when I think about it, I categorise it as an "algorithm" operating on various data objects.
I see a B-tree as a data structure not an object per se, I still don't see how it would mean anything without its operations.
|offset0|key1|offset1|key2|offset2|key3|offset3......|
OR |key1|key2|key3...|offset0|offset1|offset2|offset3...|
OR |offset0|offset1|offset2|offset3...|key1|key2|key3...|
OR |count|key3|key1|key7...|offset0|offset1|offset2.....| <== Binary search-able keys
In all of these cases, the data structure is different but the basic B-Tree algorithm remains the same. You can implement the algorithm once (using templates (C++), macros (C), or a weakly typed language (Lisp/Scheme)) and re-use it with any and all data structures that come up later.EDIT: Forgot offset0
You usually don't have to declare types but for example in Common Lisp you can optionally declare types for better performance in speed-critical parts of the code.
Also IMHO, code is data as well. So the definition of the b-tree operations (algorithms), the tree that they operate on, and the application data within the tree are all just data (some of them just happen to have a more direct effect on run time operations).
I programmed for a long time not knowing OO and it was fine, there are other ways of encapsulation/isolation of code that works just as well. I don't really see that there's a big difference, and there's a certain element of religion about OO vs Functional. Both have their own merits in different types of applications. Still it's nice with someone who stands up for the functional approach.
2. Parameterized modules in Erlang can be seen as a form of lightweight functional OOP.
http://www.lshift.net/blog/2008/05/18/late-binding-with-erla... http://www.erlang.se/workshop/2003/paper/p29-carlsson.pdf
"Sweeping generalizations are always bad" :)
Obviously not if you use composition and if some objects are just your wrapper for your data structures.
In an OOPL data type definitions belong to objects. So I can't find all the data type definition[s] in one place.
The point is that OO programs consist of many type declarations scattered everywhere. As complexity grows, it gets harder to understand what all the types are and how they are related. The textbook "shape" and "animal" examples never come close to revealing this problem: they're oversimplified and they refer to things already familiar from the world. In other words they rely on OO's roots in simulation, where it works ok. In any complex system, though, you have to go far beyond ready-at-hand concepts. You end up declaring all sorts of arbitrary types that exhibit the problem Armstrong is talking about. There isn't even an easy way to see what they all are.
It gets harder and harder to change such programs because changing one type definition entails changing others. You can change how the internals of a class work (where it reduces to functions operating on private state), but significant overhaul of the class hierarchy itself is an order of magnitude harder than it should be (than it is in a program where you just have functions).
Armstrong's reference to keeping everything in a single file is not because he believes everything should be kept in a single file. That should be obvious, since he's obviously not an idiot. His point is how much easier it is in certain non-OO languages to understand a program's type system.
I think this is a valid criticism of OO because it cuts to the core of what people claimed was great about OO: that it helps to organize complex programs better and that it makes complex programs easier to change.
Edit: Armstrong is obviously smart, and Erlang is a real contribution, but it is also true that he is not a very good writer. I find he's the kind of writer that I have to make an effort to understand, which in his case is worth it because his intuitions are worth paying attention to. Of course not everyone need feel that way. But some of the comments in this thread seem a little knee-jerk to me.
Edit 2: Smalltalk is pretty clearly an exception to all of this. This has puzzled me for a long time. It just hit me maybe why: Smalltalk is actually not an OO language. Not in the sense we talk about OO nowadays. We've all been misled by the fact that it was the original OO language and that its creator introduced the term. Smalltalk's greatness comes from another place entirely, which is why no other OO language has equalled it.
- You can send them messages
- Their state can be mutated through receipt of messages
- They have private state
- Their functions are associated with the data that they operate on
His rant is partially correct (at best), especially looking at OOP from a functional programming point of view.
However, developers in the real world who doesn't focus on concurrent programming have different needs.
I'll comment his objections from a C++ perspective (which is a multi-paradigm language, not purely OO):
Objection 1 - Data structure and functions should not be bound together - you are free to store all your data structures in a single file, and access them from anywhere. However, using classes will let you hide some of the data structures which are not necessary globally, reducing the cognitive load.
Objection 2 - Everything has to be an object - Not in C++. You use classes where appropriate, and ordinary functions elsewhere. I agree that everything shouldn't be an object.
Objection 3 - In an OOPL data type definitions are spread out all over the place. - similar to objection 1. You CAN store all definitions in a single include file, but for large projects this can be a LOT of data definitions, where many/most of them are only relevant for parts of the applications. Hiding data definitions in separate classes will make it easier to focus on the RELEVANT data depending on which class/function you use.
Objection 4 - Objects have private state. - again, similar to 1 and 3. "State is the root of all evil". If you store all data in a single file, then you have one giant global mess. By assigning them to a class, you clearly state the responsibility and scope of the variables. A private state is nobody's business but the class. You don't HAVE to have private state for objects,but then you either need to pass all data to each function, or use global variables instead.
For Erlang (functional programming) these objections make (some) sense since you DON'T want any state whatsoever to facilitate concurrency.
However, saying that OO sucks is stupid, and the four reasons for why OO was popular is not even worth responding to. I don't think functional programming will become more popular due to this rant....
Over time, I'm using less and less OO and more and more functional, so... that tells me OO wasn't all it was cracked up to be.
The biggest problem I see with OO is that it doesn't map well to relational models and OO databases aren't that popular.
In case anyone was wondering, yes, the functional community loves namespacing, encapsulation, and creation of new data types with functions specifically intended for use on them.
The real question is: How do we express relationships between data types...
Do we use subtyping? (OO yes, functional no).
How do we allow multiple types to see each others privates? (Friends/inheritance in OO, modules in Haskell and ML).
How do we do polymorphism over static data/functions? (Basically, doesn't exist in OO... functors in ML and typeclasses in Haskell).
The immutability debate is another discussion entirely.
I find it best to use the tool that is appropriate for the task. This is true even though you might be able to force fit a particular tool to a mismatched task. Saying that there is only one true way is always wrong. Especially since any of the ways we have are at best only good enough for some tasks, mediocre for many, and really horrible for others.
The title should be translated to "Why One True Ways Suck" or on a really bad day "Everything Sucks".
In the meantime read http://www.paulgraham.com/reesoo.html
Be neutral. Think. Could Joe have some valid arguments ? As someone said, patch your brain into Net Neutral.
If you need really fast code, use assembler. You have that choice. Code at the level required for the task.
I believe this is a very fundamental feature of how we think about the world and it's totally unlike OO. But I also think that it doesn't matter at all. Formal systems don't need to resemble language or the way we think about the world (or the way we think we think about the world)
We don't naturally think like Snoopy swearing, but regular expressions are still a very productive tool for someone who has learned to think differently than he would naturally do. The same goes for maths and many other tools.
I'm very skeptical about OO, but that's not because it doesn't match the way I think. I'm skeptical because OO APIs force me to know things I don't want to know. They constantly make me think about which class has a particular function.
But the rationale for putting a function in one class or another is based on implementation considerations that are of no concern to me as a user of an API. It's mostly about which object's state is affected most, how dependencies are managed, what types of changes are expected, and so on.
I've given this example elsewhere but here's the short version again: Say you want to initiate the sale of a property via some API. There are many objects involved: A buyer, a seller, a contract, an estate agent and a property. Which of the five classes involved contains the method you're looking for?
It could be in any of these classes and I don't want to think about it. I want a sell(property, buyer, seller, contract, agent) function and the API should figure out its own state and dependency matters.
This article is Not Even Wrong. It's not close enough to even being wrong to warrant a thought through response. The author needs to go and learn how to do OO properly before making wild declarations about "Why OO sucks".
================
Edit: worth clarifying this as I've just realised that this guy is supposed to be the author of a programming language (quite worrying if he is!)
As I said, this is too far from even being wrong to be worth really debating point by point, so I'll just poke a few holes to demonstrate that I'm not just flaming:
1) functions and data structures belong in totally different worlds
Anyone who's even glanced at SICP knows that the distinction between data and functions are vague at best. Certainly they are tightly coupled concepts.
------------
2) Functions are understood as black boxes that transform inputs to outputs. If I understand the input and the output then I have understood the function. This does not mean to say that I could have written the function.
How does that have anything to do with the age of the captain? Not only the author's main point doesn't make sense, but he feels the latitude to make tangents about unrelated things that don't really have an impact on his obviously controversial main point.
--------------
3) The "time" example that he gives...
In this example, he basically creates an ad-hoc object made of methods that return data. The only difference between this and proper OO is that this structure doesn't benefit from all the advantages of OO. But it's still a tightly coupled structure made of data (the values) and functions (the methods to access those values).
--------------
4) In an OOPL data type definitions belong to objects. So I can't find all the data type definition in one place. In Erlang or C I can define all my data types in a single include file or data dictionary.
So now language design should be driven by whether you have a decent editor that allows you to manage more than 1 file? Seriously, the "Everything in 1 file" anti-pattern is not even worth arguing about... When someone makes such a dumb point, how can you take the rest of the article seriously?
--------------
I could go on, but I think I'm proven my point. Either this article was not written by Joe Armstrong, or it casts severe doubt on the sanity of Erlang, or he's written it to troll the OO community.
#2 thats a core concept behind functional programming thats seperate from oo, most people dont realise that with object methods, the object is an input to that function, not just its parameters
I explained on irc for #3, but for everyone else, those type specifications are not data structures, there is no coupling nor any methods invoked, they are purely type specification that describe the datatype, useful for (optional) static type analysis and documentation, they arent equivocal to typedefs, which are datastructures (those are records in erlang).
#4 it is not everything in one file, it is everything related in one file, again this is just the ability to separate the data from the function that act upon it
It might be worth taking a second to see if you actually understand the points he is making as opposed to attacking them, before you edited in the counter arguments this was just an ad hominem attack.
As I was reading the article, I could recall moments from my past experience agreeing with every single point the article is making about OO. The article neatly summarizes most of my grievances with Java the language. And only someone with plenty of experience with OO could have made such a succinct summary.
1. X used to be regarded highly but turns out to be imperfect or suboptimal for certain problem domains.
2. Write "why X sucks" blog post.
3. Profit!
90+% of the time, when you create an object, what you really want is a struct/record. Most methods should really be static. Most of the power that does come from OO comes from its ability to foster a hierarchical type system in an extensible way.
OO smells like something that was invented to give "business" types the illusion that they can understand code without reading it in detail. "This is 'like a' Chair."
I don't. I've been fortunate enough to use a language that doesn't force the "everything's an object" mantra.
The two real advantages of OOP (from my POV anyway) are encapsulation (minimal interface) and convenient syntax: the first parameter turns to be a prefix so it resembles human language, a question of usability really.
The latter is a mixed blessing. It's often very convenient, but sometimes there isn't a clear answer which object should be the primary one in a binary operation. (This is particularly troublesome with non-commutative operations.)
Lua's syntactic sugar for this ( obj:method(args) vs method(obj, args) ) seems like a good balance; it's explicit that it's just sugar for convenience, but you aren't required to use it.
Yes, it's more or less the same, but you can define public and private parts for individual objects. The good part of encapsulation is separating interface from implementation: you can publish a data type without giving access to its innards.
sometimes there isn't a clear answer which object should be the primary one in a binary operation
I really haven't found it to be a problem in practice.
And you can do that in a good module system, too. Look at OCaml's. (http://caml.inria.fr/pub/docs/manual-ocaml/manual004.html)
Also, look at the functors - modules are first class, so you can write functions that take a module and return a (usually more specific or generated) module.
"ubiquitous ... data structures representing times (which)
can be manipulated by any function in the system."
Obviously the thought of a one single encapsulated DateTime class which manipulates time and dates (correctly) for us is a horrible idea.Invoices don't do shit.
We need both.
But then how would you attach makelove() or fight() or discuss() to a single Human? When do we use TweedHeads.discuss(visitor4rmindia) vs visitor4rmindia.discuss(TweedHeads)?
I think the more interesting observation to make is: what is the type of the argument to discuss? SentientBeing? CanDiscussMixin?
In the first case, I bet you could imagine objects to "discuss" with that would only awkwardly inherit from SentientBeing, and in the second, your major objects will quickly grow loads of mixins. Of course there's also the "implements interface" appproach.
I think all the models break down at some point...maybe the choice of which one to use is dependent on the problem.
discuss (TweedHeads, visitor4rmindia);
This, to me, underlines some of the problems of the OOPS approach. Everything starts "looking like" methods even when they would be more natural as simple functions.This is very simple to prove: give a group of experienced coders the same 2 problems to solve: one taken from a real world object, and the other purely theoretic; both to realize in OO. This experiment would show that the real world object problem is solved in a similar manner, and the theoretic one in all different ways...
And yes, I use OO, but only with the libraries written in OO style, and never in my own code.
Princeps autum justice, ille in parce est.