Was object-oriented programming a failure? (2015)
quora.com
quora.com
OOP is a tool. And generally a useful one. Every new programming paradigm at its inception was going to Save The World and make programming A Better Place. Turns out, every one if these paradigms is a tool with uses at various times.
So OOP might only be useful in a limited number of cases. I find it comfortable for categorizing functions: "Here's all the stuff you can do with a String" or "All windows behave like this..." And if your language is dynamic enough, you can add functionality to all Strings, not just your String subclass.
I don't typically find OOP useful for processing many records of data. Perhaps I might compose an object from a record of data so that a user might edit their profile in a GUI, but batch processing doesn't usually lend itself to OOP. But that's my experience and opinion; I bet someone has a good use case for using OOP while dealing with copious amounts of data.
Back to the topic at hand: Was OOP a failure? No more than lathes are failures. Not every handyman needs a lathe; not every programmer needs OOP. But when you do need it, it's a useful tool.
Consider web frameworks. Given a choice I select React and similar functional frameworks than OOP based ones. They are just more productive and scale better.
Or consider QT. My colleagues with a lot of QT experience use QML to create GUI. They avoid any C++ GUI code unless it is really a must.
That's because there are no decent OO based web UIs, because the web development experience and stack is a total mess to begin with.
You can see the disastrous implications of this feature with the ID mangling needed for Asp.net webforms, and the utterly miserable workflow that creates.
The worst part is that everything else about the original ASP.Net was mostly really good ... but Web Controls completely destroyed it's reputation.
I am now learning Rust, and for this purpose I am trying to implement a small TUI (Text User Interface) framework. In order to get some ideas, I dug in my library and found the Turbo Vision User's Manual that was bundled with Borland Pascal 7.0 [1]. Reading it after so many years made me wonder how could they create such a complex and well-thought framework that was truly usable for your own stuff, given the technology and knowledge available 30 years ago. The text often underlines that a lot of features were possible thanks to the OOP constructs implemented in Turbo Pascal, and I believe them. I think that OOP importance might have faded today, but its historical significance for the development of GUIs and TUIs should not be underestimated.
P.S. I am trying to implement my library in Rust, which is not an OOP language, and so far have found no blocking point: traits and containers are more than adequate replacements for the kind of polymorphism and inheritance used in libraries like Turbo Vision. But my point still stands: once we learned that polymorphism and inheritance were useful to solve some specific problems, we have derived better ways to achieve the same purpose.
[1] https://archive.org/details/bitsavers_borlandTurrogrammingGu...
Its gotten so bad that I feel strongly about killing the browser-based ideology and just returning to native clients for everything .. I mean, its really not an easy decision to "do the whole GUI in Web", even though by now it should be. It just seems like such a mess by comparison with where we should be by now, given the work done decades ago ..
Because there are many low skill, poorly-informed people who write code still believe these things. In particular that "OOP" is automatically equivalent to "better quality code." It’s a sort of cargo cultism that is present in a few places.
It's dogma either way. There is no single true religion just advantages and disadvantages
Quality in practical outcomes for building projects, that is, not code asthetics.
The problem is that 'functional code' means 'spagetti code' for many 'low skill' people, and 'write once maintain never' perl style code for many other 'smart' people.
Both are rubbish, terrible low quality useless code outcomes.
OOP doesn't fix everything, but its a great lowest common demoninator for code that does the job. Its not exciting. It has stateful bugs. ...but anyone can do it and it gets the job done at scale; orders of magnitude better than any other coding paradigm I'm aware of.
You want a hard problem to solve?
Go figure out a way to make functional programming scale the same way, you'll start a revolution.
...but in my experience, its the frustrated 'smart' folk who want to write their maintain-never code who push FP as a replacement for OOP.
I don't see how that is the case. Even in the most poorly written function you have code that's working mostly on it's arguments (and maybe some global or something, since it is poorly written) and then returns a value. How is this function connected to the surrounding code? We know the answer, it's called in a single expression and given arguments. You have a single isolated call for a single run of the function.
In OOP you have to both instantiate a class and then call it's methods. By simple line count it is twice as complicated to call as a function, and it is twice as coupled to the code that calls it.
When I look into the internals of functional code I often find myself in a single isolated function and the functions tend to be on the short side. When I look into the internals of OOP code I often find myself in a large method that is part of a class in an inheritance hierarchy and the method uses a bunch of globals (err, I mean "object variables") and I have to read several other methods to understand what's happening, because the method I'm interested in depends on the state left over from other method calls.
In short, objects are a lot more powerful than functions, and thus "low skill" developers have a lot more rope to make spaghetti with.
In OOP you have to both instantiate a class and then call
it's methods. By simple line count it is twice as
complicated to call as a function, and it is twice as
coupled to the code that calls it.
And in FP a state falls out of the sky?> a single isolated function and the functions tend to be on the short side
I can almost guarantee that code was not written by an inexperienced or low skill coder.
If it was, you'd have a large complex function that took large complex state as its input, and probably maintained some kind of hidden internal state in some kind of async deferred loop after it returned or some combination of the above.
Have you worked with inexperienced FP programmers?
It's not fun.
Inexperienced OOP is a lot easier to deal with, because the entity level API validation is all you care about; you don't care about how hideously the internals were implemented as long as the tests pass. There can be any number of bugs and spaghetti inside the object, but you don't care, because you don't see it (until it manifests as bugs; but then you just add more tests and make them go away until all the tests pass).
I think there's a pragmatic aspect to OOP that involves large scale organizations collaborating on projects that you're not taking into account.
You could argue that a test suite for FP does the same job, and maybe it does... but the point here is that someone has to define the module level interface, and how it behaves as a state machine (regardless of if the state comes in 'pure' and is passed around or is encapsulated internally); and that's OOP, not an inheritance hierarchy.
No pure FP language will allow you to do that without the function's type signature reflecting that - it would need to return its value in some kind of state/async monad.
Now, if you're talking about FP in a traditionally OOP language, then I agree with you, but that's an apples to oranges comparison.
> Inexperienced OOP is a lot easier to deal with, because the entity level API validation is all you care about; you don't care about how hideously the internals were implemented as long as the tests pass. There can be any number of bugs and spaghetti inside the object, but you don't care, because you don't see it (until it manifests as bugs; but then you just add more tests and make them go away until all the tests pass).
This approach doesn't work for things that are difficult to test. e.g. you can only ensure your types are thread safe by reasoning about their use of locks, volatile, etc., because any attempt to test this is dependent on the implementation details of your runtime (e.g. the JVM) and the amount of load its under. Best case you get a flaky test, worst case you miss a bug that blows up in production.
But this is a tangent, because TDD is not specific to OOP - it works just as well with FP.
> but the point here is that someone has to define the module level interface, and how it behaves as a state machine (regardless of if the state comes in 'pure' and is passed around or is encapsulated internally); and that's OOP, not an inheritance hierarchy.
Pure state is an oxymoron. The idea is not that you pass a mutable argument in, but that you don't have the ability to mutate data at all. At best, you can return some instructions on how to mutate (e.g. the state monad), but its the consumer of your code who decides what to do with that, which prevents you from hiding side-effects, and simplifies testing.
Defining a module level interface and behaviour is just API design, not OOP.
This about sums up my experience with these discussions, too. I know a lot of people who are absolutely convinced that C++ "is better" than C because you can write object-oriented code in it -- you have native support for "classes and stuff".
The whole "is better than" thing is a little childish to begin with, but this argument is on the thin and confusing border between "meaningless" and "but why?"
That's a module. This scope creep is part of the problem with OOP, as it slowly encompasses features that are orthogonal to their main purpose.
OOP is mainly about procedural abstraction: any type implementing a protocol (set of methods) should be indistinguishable from any other type that implements that same protocol. This enables the open extension that characterizes OOP.
So amending your statement to make it truly about objects, "here's all the stuff you can do with String-like values".
Naturally you can do the same in FP with modules, but honestly, how different it is from OOP then (considering that you can use some form of polymorphism)?
For me OOP and FP stand for abstract concepts, not as specific languages.
This is not a defining feature of OOP, because it's a characteristic of abstract data types which predate OO. William Cook managed to distill the essence quite clearly I think [1].
Well, that's what the OP in OOP means. It means you are oriented towards objects, and away from everything else.
What's the difference between writing a test and doing TDD? In TDD the tests are the center of your universe.
Plus there's the fact that "Everything is an Object" was a very common slogan for OOP.
Of course you can write non-objects in your object oriented code... An object is made of non-objects! But OOP means when you are asking architecture questions you sort out your names and your methods first, and functions fill in the gaps.
Compare to (old school) JavaScript where everything is a function to start, and you add prototypes and methods only when you really need them. (RIP JavaScript, long live OOP/ES6)
OOP is also a tool that others have used to build things that many of us use all the time (I'm looking at you DOM API and CSS) so it's difficult to truly escape from OOP. You may never like it, but learning it will deepen your understanding of many other tools.
Similarly, unfettered access to state was seen to be problematic. If you are implementing a skiplist, you don't want users of the list to be manipulating the links directly. You want to expose only the operations, and have the state accessible only to the implementation. That's encapsulation and modularization. And then inheritance provides a way to modify modularization slightly.
OO is really just a few ideas about organizing code and data, and you are free to use them or not, just as you are still free to use gotos. The hype got out of hand. I think OO madness was the first vastly overhyped software trend. Things got bad with all the Janitor ISA Employee examples got trotted out to explain OO to managers, and then some people believed OO was claiming to excuse programmers from actually developing efficient code. Anyone using, say, Java to build a complex software system realizes that OO is used to model internal things that never get anywhere near a customer-facing API.
There were zealots, there were AbstractFactoryFactoryAdapterFactory nuts, and the backlash against them is totally justified. But the core ideas of OO are just obviously good ones, and usable in non-OO languages too.
Only ignoring the fact that almost ALL programs we care about, from OSes (Windows, OS X) to Games, and from Office applications (MS Office, Open Office) to graphics apps (Photoshop, etc) to NLEs (Premiere, FCPX, Avid), to IDEs (Eclipse, IDEA, Visual Studio), to major compilers, JITs and runtimes (LLVM, Oracle HotSpot, MS CLR) etc, including all major browsers people browse the web through (Explorer, Safari, Chrome, Firefox) and all major Javascript engines that power the web are written in an Object-Oriented way, and usually in the way C++ defines it.
Also true, graphical applications have elements that lend themselves to "object" abstractions. But that's not all that goes on under the hood.
I have watched numerous talks from game architects (John Carmack, Jonathan Blow, Casey Muratori, Mike Acton, and many less well-known) and OO just isn't a big concern. "Data-oriented Design" is (to offer a different buzzword).
Here's what Fred Brooks (Mythical Man Month and, more pertinently No Silver Bullet) said at a panel 20 years after NSB:
A burst of papers came and said “Oh yes, there is a silver bullet, and here it is, this is mine!” When ‘96 came and I was writing the new edition of The Mythical Man-Month and did a chapter on that, there was not yet a 10 fold improvement in productivity in software engineering. I would question whether there has been any single technique that has done so since. If so, I would guess it is object oriented programming, as a matter of fact.
* syntax that favours inheritance over composition * inability to specify polymorphism over two input types * inability to specify polymorphism over return type (Both of these are symptoms of close identification of type and table) * Syntax that makes generics unwieldy to the point that many think they're just for collections * Inability to make third-party types expose your own interfaces (Clojure manages this. Some languages manage this through monkey-patching, but that's a cure worse than the disease.)
OO is broken. The biggest problem is that we're all so invested in it we have trouble seeing there's another way.
Just think how many Java tutorials started with the procedural "Hello World" example and then proceeded to introduce objects and taught the idea of encapsulation by reminding how important it is to protect the private members by creating a list of get/set methods that are used to access them.
This leads to objects with interface like this:
class Person {
public void setStomachContents(...);
public void getStomachContents(...);
public void setThoughts(...);
}
While I believe a "proper" object would have interface more like this: class Person {
public void eat(...);
public void listen(...);
public Event<ProcessedFoodItem> output;
}Whether or not that is a proper solution depends upon the requirements.
A more useful example for beginners would be showing when you want an "eat" on a person class vs an "eaten by" on a food class vs when you need to pull out something more heavy handed.
Eg if your only axis of change is the foods you eat, and behavior is based primarily upon food, person as a dumb bag of data is likely the correct answer. Your behavior should probably be in a food class.
[1] https://www.amazon.com/Domain-Driven-Design-Tackling-Complex...
[2] https://www.microsoftpressstore.com/store/object-thinking-97...
Last I checked, nearly everyone is doing OOP.
After a couple decades of coding I've come to the conclusion that "easy" is almost always better. There isn't much code I wrote 20 years ago that anyone is using today but where it is in use it runs without needing to be updated or maintained.
Most of my apps are now mostly written in Javascript, and I write them very much the same as I did in Perl. I use a mix of both "functional" and "object oriented" methods and don't worry about what others think of it.
Of course, I'm not doing "AI" or anything close to that. I make apps for business management and they care a whole lot more about the UI, reliability, and security, than what the code looks like. The only thing I ever get when showing them code is a glassy eyed look of despair and worry that I won't stop.
Sometimes your problem is inherently a distributed state problem, and that's fine. But OO doesn't care if this is sometimes true. OO pushes people toward distributed state architecture, and it unnecessarily encourages excess state splitting even if the problem inherencies don't warrant it.
OOP is not a failure. But overengineering is another thing. (Sometimes I heard "Never write static function/method! Do it in OOP way!", though it doesn't make the code simpler or cleaner)
For one, many implementations have actually different names (such as append/add/__setitem__ in python, or push_back/insert/operator[] in C++).
For another, data structures are important for efficiency. It doesn't make sense to abstract those. You shouldn't use an array where a tree or hashmap is more efficient, etc.
By contrast, look at how polymorphism in datastructures is actually done in C++, i.e. STL. That's not your typical OO approach.
In contrast, most (typed) FP languages support typeclasses / traits, which makes a lot of code considerably simpler.
Data hiding (and immutability) are good thing even as they sometimes force you duplicate data. So is separation between public and private, no matter what exact syntax is used to achieve it. Interfaces when done right allows you to reduce cognitive overload.
I could go on, but really oop was huge improvement over whar existed before - especially when used where it fits well. Functional programming is improvement too - especially when used where it fits - which is often place where oop fits less. The problem is programmers who don't bother to learn them and then they complain about not understanding them.
Classes that hold data have no methods (beyond getters/setters). Classes that implement behavior have no mutable state; they exist as namespaces for the functionality that they provide.
I've been quite successful with this approach. It leads to code that is easier to understand than a big ball of stateful objects.
Utter nightmare. Loads of repeated code as new programmers didn't know methods existed, loads of extraneous DTO objects, lots of nested single line methods, massive code bloat. Hard to find what actually does stuff, hard to use built in editor functionality.
It's really not a good tactic imo.
I'm undoing it as I go, at one point after having worked on it nearly 4 months according to git I'd still had a net negative on total lines of code, having added a ton of new functionality.
Admittedly, it's one of those projects which has had a bunch of freelancers/contractors work on it, but I personally really don't see what it added to move the methods off the classes apart from confusion, code duplication and code bloat.
I see the need for something, on the startup + enterprisey projects I work on you always seem to end up with at least one mega class that ends up being a beast, the Order, Person, Customer, Job or Project objects are usual culprits, but most other classes don't need much past a few basic methods. I'm just not convinced this programming tactic is it having now seen it in the wild.
Moving the methods off the classes would actually lead to less code duplication -- as now different data that need the same treatment can be handled with the same methods.
"Loads of repeated code as new programmers didn't know methods existed" seems like a bigger issue here...
That just doesn't happen enough in real life to design around.
In the rare instances when you do need to that, it's trivial to handle with interfaces and a wrapped method that calls a shared function.
On the contrary, I think it does. Especially for web, enterprise and application programming, 90% of the logic is the same tired data transformations.
Have you got any examples?
I've never had to write identical code on an Order class as a Person class. Can you give me an example of 90% of the logic being the same on an Order and a Person class?
Like in C#, the entity framework has mainly done away with all the "tired data transformations". The equivalents like Hibernate, ActiveRecord, etc. have done the same in other languages (and were the trailblazers). When I dip into a data transformation these days it's complex, hand-coded SQL, the tricky bit that takes time, with a simple data class to handle strong typing, trivial to write in a minute or two with snippets and auto-completion.
Sorry, why the complaining? We were talking on a more abstract level. Yourself just gave some anecdote about "Someone did this on a project I work on (C#). Utter nightmare. Loads of repeated code as new programmers didn't know methods existed, loads of extraneous DTO objects, lots of nested single line methods, massive code bloat", that's hardly an example either.
>I've never had to write identical code on an Order class as a Person class. Can you give me an example of 90% of the logic being the same on an Order and a Person class?
Apart from the data contained without, which process would not be the same? Most high level operations would be exactly the same: filtering would be the same (it just needs to accept a predicate), ordering would be the same, serialization would be the same, etc etc.
For a very simple example, you don't need:
persons.sort("age", sort.DESC)
orders.sort("created_at", sort.DESC)
etc, you just need: sortedPersons = sort(persons, cur -> cur.age, sort.DESC)
sortedOrders = sort(orders, cur -> cur.created_at, sort.DESC)
And the same function can work with 100s of other classes and containers. db.Orders.Sort(o => o.StartDate)
I've also never found ordering code to be the bulk of my logic, but whatever.Anyway, generics. Without having to muck around with funky designs for your classes.
The only caveat to this approach is that for some cases you will need a state accumulator of some sort. But those state manipulations are isolated and easy to deal with.
You assert that code based on this approach is easier to understand, but you don't give a reason why this is so.
This sounds a lot like a pre-OO approach to organizing code and data, and I don't understand the benefits. To use the tried-and-true example: I want my stack state and code to go together. Why would I want to expose the stack state, and keep the code manipulating it separate?
C structs were used obviously to hold data. Structs were created after analyzing the data to be stored, determining multiplicities, lifetime, etc.
C functions were frequently large and handled numerous concerns. Programmers usually started with one function and split off other functions as needed mostly to take advantage of commonality and to avoid deep nesting.
o.f(a,b,c)
to f(o,a,b,c)
Remove methods and use the object as a data structure.Traditional procedural, structured programming works this way. You see it at a small scale in countless scripts written in languages like Perl or Python, and at a larger scale in big C programs.
Functional programming also works this way, but typically is even more explicit about it.
The key point is that usually you still want to separate the responsibilities of your program in a modular way, with clean interfaces and hidden implementations. You just use other tools to do it, typically by defining your data structures and algorithms directly, instead of bundling them together in a class or your language’s equivalent.
At first glance that might seem restrictive or less powerful, but in fact it’s the opposite, because it removes OO’s inherent bias towards algorithms where one object is special. I commented about this last point once before, if you’re interested: https://news.ycombinator.com/item?id=8690746
If it helps, you might consider that when you call a method on an object in a typical class-based language, what’s really happening under the hood is that (a) you’re passing an implicit pointer/reference to the object as an extra parameter to the function you call, and (b) which function you call may itself be determined by a look-up process based on the type of the object. If your language didn’t support those functions as built-in features, you could just as well pass any required data from the “object” in and back out again explicitly through function parameters and return values, just as you do for any other data you’re processing or generating in that function, and you could use some sort of look-up table to decide which function to call based on some form of tagging each object with its “type”. Indeed, plenty of people were doing this in C, before what we now call C++ came along and made it more convenient by building the pattern into the language.
P.S. Link to the original interview: http://www.stlport.org/resources/StepanovUSA.html
It's more encompassing today than the traditional model of sending messages to objects to make them change their internal state. This model doesn't exactly lend itself to being great in managing complexity either.
What about the concepts of Encapsulation, Inheritance And Polymorphism? In the form that they are often employed create more complexity and problems than they solve in any non-trivial scope.
It's often said that naming things is one of the hardest problems in computer science. This is particularly applicable to OO style of programming, where the available code reuse capabilities are so limited as to force a programmer to write the same blocks of code over, and over and over again in an increasingly complex codebases, stitching up names along. Seriously, just grep an OO codebase for terms such as "Adapter", or "Strategy".
You should also find it ironic that the only semblance of progress in the OOP landscape in the past decade had nothing at all to do with OOP concepts. But rather it's the incorporation of ideas from Functional Programming into mainstream OO languages that have led to the biggest productivity gains.
Maybe it's a good tool in some contexts, fine. But as the de facto standard across an entire industry of solving problems with Software? A mediocre solution at best!
Object-oriented programming wasn't a failure.
It's an ongoing one.
Now that computer hardware manufacturers are moving toward providing more CPU cores that allow for higher degrees of parallelization within single applications - development strategies that make it easier to keep parallel processes from working against each other are becoming more preferable.
Looking at OOP's success as binary is inappropriate. OOP was the right tool for the time and hardware that was available. It served its purpose, but unless its shortcomings on modern and near-future hardware can be addressed... it will fall out of vogue and be regarded as the wrong choice to make.
There's an easy case to be made that starting with a functional programming, easily parallelizable language from the beginning helps to mitigate the growing pains that happen later when you discover that you need to take better advantage of the hardware.
It was probably C++ (and later Java) that interpreted OOP as a mandate that your types should fit in some sort of hierarchy. Inheritance (usually with polymorphism) was seen as a primary way to reuse common code. Fortunately, more and more people are realizing the problems that arise when the real world doesn't map easily to the overly-simple inheritance hierarchy. This type of OOP failed a long time ago.
On the other hand, OOP in the original Alan Kay sense[1][2] is about message passing. This happily lives as a key part of languages such as Ruby.
[1] "I invented the term Object-Oriented and I can tell you I did not have C++ in mind."
Objects were supposed to encapsulate data (state) and expose operations that would mutate that state in a controlled fashion. But, we then imposed this immutability requirement, whereby we create these objects very explicitly with long-winded builder patterns, then make copies of the data when necessary,etc. At this point, we have effectively externalized the logic around the object's state and the object itself is nothing more than a C-style struct.
Sure, immutability has its advantages, but we should acknowledge that it's not a OO-friendly concept.
To me OOP winds up looking a lot better without the inheritance
While, on the other end, immutability fundamentally undermines the very premise of OO, which is to model the domain as state and behavior vs treating objects as data wrappers to be manipulated externally.
float price = 0;
while (!vehicle.gasIsFull()){
Gas gas = this.undergroundGasTank.getGas();
vehicle.depositGas(gas);
price += gas.getAmount() * this.gasPricePerGallon;
this.display(price);
}
It would be out of place for a car or gas canister class to know how to use the undergroundGasTank object and to calculate the price themselves.Why do you say that? It is completely reasonable to define objects whose state is fixed at creation, hide a representation, and have methods that implement some abstraction. E.g., strings, complex numbers, points in a space, and possibly even more complex things like sets. All the advantages of OO apply, even for objects whose interface does not provide a way to mutate the object.
There are those who believe that all objects should be immutable, particularly at the domain level. In my view, that defeats the purpose of OO. There really is no state in this case in any meaningful sense because to get a new state, you must create a new object. So, the object is simply a collection of data.
And, even in limited use it's not strictly OO, which is a separate question from whether it can be useful. The entire purpose of OO was to model the real world. In the real world, you have a bus with passengers. When one passenger gets off, you have the same bus with one fewer passengers, not a completely new bus with all but one passenger The turn signal goes on and off vs. having a new bus with a different turn signal state.
So, at the domain level, it completely breaks the OO concept. And, even at the utility level with language constructs like Strings, immutability tends to oppose OO in the strictest sense. In fact, in many languages (like Java) the String is immutable for technical reasons (security, etc.), not because it's particularly good OO design. To wit, you now need a completely parallel "real" class (StringBuilder) to do the real "object work" of the String (append, delete, etc.) without creating object copies everywhere.
Where OO is concerned, immutability is generally an encumberance.
Says who? I.e., do you have any references?
It is easy to give examples arguing either side. I agree that your bus example is best modeled as a mutable object. The same bus can have different passengers over time. But a complex number is an example that I think is better thought of as immutable.
You really lost me with your discussion of Strings. Why does "immutability tend to oppose OO in the strictest sense"? I like the distinction between String and StringBuilder, because I want to be able to share a String without worrying about the possibility of an update through some other reference. (This is not an argument about whether String/StringBuilder is OO or not, just a comment on the design of the classes.) Just because it is possible to define mutable objects, it does not follow that all objects should be mutable. By your logic, Integer should also be mutable?
My statement rested on the original intent of the OO paradigm, which I described in the subsequent paragraph, including the bus example.
And, the reason you agree that the bus is best modeled as mutable is because it represents this original intent!
You can (and did) make an argument for why you like immutability. There are valid reasons to want to be assured that the data doesn't change, but that doesn't make it good OO design vs just being overlaid to achieve some other purpose. On a side note, it also introduces the question of which copy of the object/state is referenced elsewhere and what the possible inconsistency means. That again points to the tension between OO and immutability.
So, never say never, but the more immutability you have, the more difficult it is to pull off a good OO design overall. If you imagine that every object is immutable, then you really won't have an OO design at all.
>Integer should also be mutable?
I see no inherent reason number objects must be immutable. In fact, if you've ever done math with Java's related BigDecimal class, you quickly realize how much cleaner and less error-prone it would be if that class was mutable, especially when performing a series of operations. That thing has internal state, so why am I forced to continuously assign the results of each operation to a completely new object vs simply having those operations update state?
> That thing has internal state, so why am I forced to continuously assign the results of each operation to a completely new object vs simply having those operations update state?
The abstract idea of an integer is immutable. Changing 3 to 4 is nonsensical. (It is not analogous to modeling a bus by a Bus object with operations reflecting the coming and going of passengers.) That's why the Java object named Integer should be immutable, and similarly for all the other Number classes, including BigDecimal.
Now if there were an IntegerBuffer class, then sure, there should be methods to change state.
Your argument is that there is internal state, state can be mutable, therefore every object should be mutable. My argument is that the nature of some objects is immutable, and designing a class to reflect that nature is preferable, and not at all at odds with OO.
In other words, you don't generally need:
BigDecimal three = new BigDecimal(3);
but
BigDecimal sum = new BigDecimal(0);
Followed by some operation(s).
Sure, depending on naming, you can create confusion with mutable numbers, but that's true for immutable objects as well.
At the end of the day, I am not saying that immutables have no use and one immutable class outright destroys your OO design. I am saying that there is a tension that exists between the two that can easily be seen if you envision a design with 100% immutable classes in the context of OO's raison d'etre.
Bringing it back to the OP, my bigger point was that all of the work involved and "side effects" of layering on immutability are an example of how people assess OO's success through the lens of things that were later added, but were not an intrinsic part of the OO paradigm in the first place.
I realized, so many of my method calls on my objects were only ever used once. Sometimes I'd create a class which just had one method. I had to assign a good name to this class, and it's method. I've since switched to heavy use of extension methods in Kotlin, and found I was mocking less for many tests, and writing less code that accomplished more. And variable naming was simpler.
At the end of the day, a good blend of OOP and functional programming are where it's at
We just don't see it because FP is not as common, but spend any amount of time with some Haskell programmers and you'll see the hazard is just as real.
The right level of OO is "very little". Some mostly-FP languages like F# are closer to the ideal level of OO than the mostly-OO languages.
We learned a lot from the Java age, and new languages show it (Swift, Rust, ...)
I have never dealt with code in practice that was less maintainable and understandable because of OO. Do you have a specific example?
Don't get me wrong - this is not an inherent drawback with OO, this is because it's applied incorrectly. Making bad abstractions or a class hierarchy 10 levels deep is bad, and it's possible that the same inexperienced developer would just make another mistake in another paradigm, leading to some other problem instead. My issue is perhaps more with the design of popular OO languages (Java, C#, C++) than with OO as a concept.
A very good OO language could be made by simply encouraging best practices and removing the biggest warts (no null, immutability by default, support for sum types, encourage pure functions, discourage deep class hierarchies...).
Experienced programmers can write maintainable code in any paradigm, but I think the paradigms should be judged by how well it helps average programmers write maintainable code.
I'd say excessively deep class hierarchy or bad abstractions are still a lot better than similarly "bad" imperative or other code, not sure about functional though.
That's the benefit of it. On a type abstraction level it's perfect to write Organism > Animal > Dog. That's the benefit of inheritance. The drawbacks are more subtle though and manifest like mad stacktraces that are very brittle to change (what happens when I change what I return from this method? Who are the downstream and upstream users? What does this stacktrace mean? When it becomes difficult to reason about is when you have all these kinds of abstractions executing at the same time - which easily happens.
at Dog.CreateName at Animal.CreateName at Dog.SetNewName at Animal.ResetName at Animal.Create at Organism.Create...
His point is in slavish love of OOP concepts. Of excessive abstraction and storing state everywhere. I've worked on multiple projects where I can only describe the code as "bureaucratic". Oodles of little classes imperiously demanding you work through them to get anywhere inspite of terrible mismatches and the overcomplication it creates.
The good thing about these OO-inspired class based languages (C++, Java, etc) is that it has forced people to write their spaghetti code inside modules (classes) with documented interface. They also made using algebraic data types common.
"The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be."
To some point I can follow the argumentation of the author. However, I do not see any superior alternative to the OO paradigm especially combined with Unit testing. Thus, HN, what are the alternatives to OO ?
I would not call these alternatives, as others have commented these are all tools, no one is superior. In my opinion, C++ is a failure. I wouldn't call Object Oriented a failure. I do think OO could be way better.
In this sense, I think there's a distinction between textbook OO and cultural OO. One is a benign tool in a toolbox that you can reach for and the other is <strawman>when people reject merge requests because 'it looks like type A 'is-a' type B. So make it inherit </strawman>'.
So, yes. OOP, being that absolutely everything needs to be an object is absurd and failed. But the people who take it the other direction and say nothing should be an object are just writing even more complicated, more difficult to reason code.
Use tools as tools and drop the ideologies, for a happier and brighter world.
The point of OOP isn't to predict the future. You should be actively aware that you cannot know the future and leave youself room for change.
Funny that with JavaScript a rather undervalued OOP model (prototype based) got such a spread :)
"Sure 90% of OO is crap. That's because 90% of everything is crap"
Oh?