Anthropomorphism Gone Wrong: OOP
loup-vaillant.fr
loup-vaillant.fr
This is the kicker. In most large projects(100k-million+ loc) the largest issue is complexity. Object oriented programming is just systematic way to organize that complexity.
The OP says that "In production code, I would split it in several files as appropriate. Or not. It doesn't really matter. What does matter is that we can easily find those procedures whenever we want to inspect or modify them."
It 100% does matter, and having all procedures that operate on and change the state of an object in the same file is super helpful. Being able to make guarantees that certain state cannot be changes outside of the organization unit of the class(encapsulation) is super helpful. Being able to abstract over certain related but non identical functions without having to resort to switch statements is super helpful.(polymorphism) And the ability to reuse code when different pieces of data share some identical features and we want to abstract over a portion of those features is useful(inheritance).
OOP doesn't give you any abilities you didn't have before, and you can write great readable structured procedural code. But it does give you more tools in your tool box that when used correctly can help structure your code in a way that limits the burden of the software's complexity.
How many OOP programs do you know that have achieved this? None that I know. Perhaps utility libraries can get close, but no software actually solving domain-specific problems. Objects don't live on their own, in a typical OOP program there are bits of code all over the place that operate on your data objects. Finding all the ways that a personel record is modified can be a tricky problem. More so because the object is likely to be a bastardized combination of personel data and software engineering level-of-abstraction (see below).
> Being able to make guarantees that certain state cannot be changes outside of the organization unit of the class(encapsulation) is super helpful
Agreed, but woefully limited. The idea that a class should be the unit of encapsulation leads to all kinds of horrible programming idioms, leaked abstractions and interface complexities. A better solution would be to erect encapsulation barriers explicitly, rather than tying them to an unrelated domain-modelling semantic.
> Being able to abstract over certain related but non identical functions without having to resort to switch statements is super helpful.(polymorphism)
Or just polymorphic dispatch, which if you're going that way, you can add multiple dispatch and get much more power, security and expressivity.
> And the ability to reuse code when different pieces of data share some identical features and we want to abstract over a portion of those features is useful(inheritance).
Except that again, OOP encourages programmers to think of code reuse in terms of an unrelated domain-concept, which leads to class design that is motivated by functional needs, rather than domain needs. Which is again a bane of real OOP systems.
> OOP doesn't give you any abilities you didn't have before, and you can write great readable structured procedural code.
Agreed. You can write OO procedural code. I've written quite a lot of OO C, for example.
> But it does give you more tools in your tool box that when used correctly can help structure your code in a way that limits the burden of the software's complexity.
I'm skeptical that 'when used correctly' is anything but a no-true-scotsman fallacy. In practice, the way OOP pushes together concerns of domain, semantics and syntax seems to encourage code that lacks the kind of encapsulation, reuse and separability that OOP-devotees claim is the raison d'etre of their paradigm.
I bought OOP in a big way through the 90s and into the early 2000s. I'm sorry to say I think the emperor has very few clothes. The realities of the big software systems you cite seem to be very different from the clean simple benefits demonstrated on toy problems in CS classes.
Three issues I've hit time and time again, in the millions of lines of OOP I've written and maintained: one is the expression problem, a second is the fragile base class, and a third is the metaclass problem (where the domain requires three levels of modelling, but the language provides two, or provides metaclasses with semantics that 'break' the principles of OOP you mention).
Searching for all the code that touch an Employee record class is much easier than searching for all the code that touch a generic hashmap that supposes to contain the employee data.
Certainly no language I have or would develop an MIS in, regardless of paradigm.
When your objects act more like very lightweight processes ala Smalltalk or Erlang model that communicate with each other via messaging, OOP becomes a lot more than a "coat of syntax sugar over the procedural cake" and anthropomorphism sudden works very well. If you see objects as data structures with methods rather than isolated processes with message based interfaces (think actor model), it's not all that surprising anthropomorphisms don't add much value. However, when your conceptual model of objects implies each object is a concurrent and isolated process, anthropomorphisms make sense. I sometimes even give them names and imagine them as employees in an organization. Asynchronous communications can be conceptualized as inboxes and outboxes of messages and synchronous communications can be thought of one employee getting into a queue himself to personally deliver the message.
Sometimes metaphors translate surprisingly when you become a stickler for making sure all aspects of a metaphor are accounted.
http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay...
---
OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I'm not aware of them.
---
I think over the years OO just ended up meaning "whatever C++, Java and C#" implemented. But you are right, if you go back to its original design Erlang is probably more OO than any of those languages. I believe this was Joe Armstrong's tongue-in-cheek observation a while back as well.
Encapsulation and inheritance and stateful objects have their place, but the "object" metaphor can really lead down the wrong path when taken too literally (as it is in practically every course that starts with "animal" or "shape" or "car" examples).
I've heard of interviewers who say to candidates: "I have a bookshelf. Design me an OO interface." They want the candidate to create objects for Shelf, Book, etc. without knowing anything at all about what the software system is supposed to do! This is so backwards IMO that I cringe just to tell this story.
That being said, I can easily imagine it being misused by bad interviewers.
But dogs don't 'have names' in the OO sense. Humans have names for dogs. We can give a dog two names and it doesn't matter whether other dogs have two names or not. We can have a dog with no name, yet we can still call it by something as though it were a name. We can talk about the name of a dog that doesn't even exist.
OO is a valuable methodology, but you shouldn't have to lie to yourself to see that. It's a convenient way to bundle certain concepts together, and many problems do not require -- or are actively hindered -- by that bundling.
* Expiration (do all credit cards expire? Is expiration necessarily a date, or is it a predicate?) * Cardholder - referent might be another entity in your system * Is the card number encrypted? How do you represent encryption?
Most of the time I use classes and their objects as a stateless bag of related methods. Like namespaces...
On a somewhat related note, http://www.newspeaklanguage.org uses classes for all kinds of things, including namespaces.
But I have to admit, the dynamic properties of objects make them much more versatile than namespaces or modules.
If I would use them only as namespaces, by defining every of my (stateless) methods static, they would work exactly as a namespace. But when they're bound to an object instead, I can switch out implementation details easily when needed. So I would consider classes and their objects to be a more powerful version of namespaces.
These are not mutually exclusive.
<rant> YMMV
Early in the OOP fad phase, the suggestion that OOP had any very close connection with real world objects and the idea described in the OP as anthropomorphism sent me into nearly a rage of indignation -- as if I was being force fed conceptual sewage.
E.g., consider plants, billiard balls, cars, bottles in a cupboard, etc. with each represented by a software object (that is, an instance of some class) and then want to program some real world situation for those real world, physical objects, say, the first shot in a game of billiards where the balls bounce off each other and the rails and fall into pockets, where planets affect each other via gravity or collide, where cars move as in traffic, etc.; then, we ask, do the programming constructs of OOP really, naturally, automatically, or even seriously help handling the billiard ball collisions, the gravity, the traffic jams? My view was -- not really, at best not much.
Eventually I just decided to regard OOP as a glass half full and like what was there that I could like and f'get about the rest; then I regarded an instance of a class as much like what the OP calls a record or, more accurately, like a structure in PL/I.
Note: PL/I structures have a really nice advantage because all the addressing is just array addressing -- super nice, that is, simple and fast, that is, typically there is little or no need for the compiled code to follow pointers to get to variables in the structure.
Then the definition of the class was much like a based structure in PL/I where could allocate as many instances as desired. Then for the OOP methods, in PL/I just have some entry variables in the structure. The rest of OOP looked like no more than syntactic convenience (sugar). Actually, for OOP inheritance, PL/I had key word LIKE where could have a structure A and then define a structure B and for part of B say it was just like structure A.
For OOP inheritance and polymorphism, I was not impressed: So far, in my own code, I've never used inheritance if only because there will be source code dependencies likely a long way away in the code base -- e.g., might be inheriting from some class have never seen, which sounds like programming partly in the dark and risky, that is, error prone. For polymorphism, I've used it a few times -- okay, I did it with interfaces which are essentially the same as passing an entry variable in old Fortran. For encapsulation, sure, I get some of that when I use some of the classes in the Microsoft .NET Framework; if I can trust what Microsoft did, and really I have little choice, then okay. I have written a few classes where there is some encapsulation -- push the low level details out of sight where I don't have to work with them or risk using them wrong and making a mistake. E.g., have a class that reads a file and has a method that on each call returns the next token in the file -- not a biggie.
Two final points:
(1) Simplicity.
Mostly just keep everything as simple as possible but not simpler. If something is at all complicated, then write some good documentation to explain it and show that, still, actually, it really is simple. With things so simple, a lot of the tricky stuff in this and that programming language mostly don't much need.
E.g., a closure sounds a lot like passing to a function A an entry variable as an argument to a function B and where function B uses some syntactically nested scope of names to manipulate variables it has access to merely via scope of names and where the function A does not. I did that a few times in PL/I; it's tricky coding, and I'm not thrilled with it.
(2) Libraries.
The code I write is mostly just mortar or glue to hold together uses of functions and methods in classes from Microsoft, etc.
In my project, at times I've written some code that has some real conceptual content, really from some applied math I derived, but such code has been relatively short (the code via Microsoft's ASP.NET for even a relatively simple Web page can be much longer), and given the applied math in good form, the code was fast, fun, easy, and short.
So, I write some challenging code occasionally, but otherwise I end up writing a lot of routine mortar or glue code.
So, for me, the programming comes down to define storage (sometimes with classes), allocate/free (with the Microsoft managed code languages mostly can just f'get about free), assign the value of an expression, if-then-else, do-while, call-return, try-catch, and then document it so that six months later I can still understand it.
Right, the OOP examples with animals, shapes, and cars I did not regard as significantly useful or helpful in practice -- much better is just document the code, that is, explain it to a human.
</rant> YMMV
I've designed concurrent systems with many processes talking to each other. Each process has a name that describes a profession, owns certain parts of a shared database and transacts with other processes.
Sure that analogy can break down in the limits, but thinking like this allows me to get a much clearer mental picture of what's happening.
It is really not about anthropomorphizing objects, it is about where the state is kept and who can modify it how. And the right or at least the best choice heavily depends on the problem you are modeling. If you are primarily tracking the whereabouts of students it is probably a good choice that students keep track of that information and provide an interface to update it. If you are primarily interested in rooms and who is inside, it may be better to make the rooms responsible and provide an interface like classroom.enter(student).
But OO culture has an unfortunate tradition of using bad examples by using physical objects as analogies for objects in code. Cars, fruits, students, teachers, whatever. Problem is, objects in code rarely correspond to physical objects. In the cases where physical objects are represented, it is most often in the form of database entities, which are typically treated like data-objects, which makes the discussion in the example moot.
Many people seem to get this wrong, probably in part due to introductory examples doing it wrong.
Don't people who practice DDD, including Martin Fowler, commonly talk about the benefits of some of it's concepts, while modeling the Domain using Aggregrate Roots, Bounded Contexts, etc?
While less a direct connection, folks who use DDD a lot also find CQRS (command query responsibility separation) a natural fit and are often seen being used together.
Containers are also used to demonstrate OO, but they're so abstract only a compiler geek could love them.
In the end I'm left with the same conclusion. Given that there is no obvious performance gain from a procedural approach, I would opt for a design pattern that is friendlier to the programmer (the human being). It's helpful to model things this way as an organizing approach. And there are obvious benefits to maintaining state within their respective concerns.
It isn't to say OOP is the only and best answer. It very much depends on the program, but I was hoping for a more obvious win on the procedural side here.
OOP is better where you code more readably(in comparison with procedural) and where it makes you enjoy coding more. The conditions for when it does that differ from persons to person and from project to project.
I personally like OOP because it helps me focus on different abstraction levels at the same time, and it helps me stay in the flow.
Regarding the redis source code: Beautiful, truly. I wish more code was that clearly and extensively documented.
I’ve spent quite a few years coaching and dealing with developers with a heavy electronics background work in OO/C++ environments and I came to the conclusion that they would be more comfortable and competent in C. All smart people but the bells and whistles of C++ AND OOP just was confusing.
I’m surprised at some comments here claiming that OO is ‘friendlier to the programmer’, I would put money on the average developer being able to work their way around even the Linux kernel quicker than a comparably sized C++ code base with all it’s OO goodness.
What OOP was trying to solve back in the day was and still is a real problem but it annoys me how many additional problems ‘full blown’ OOP introduces.
The example offers a flawed data model. Each student is in a classroom, but it doesn't make sense to model the location as an attribute of the student -- unless it is actually a coordinate position. If it's a symbolic location (a classroom), it is much more logical to treat the classroom as a container; therefore it is the responsibility of the containers to move stuff around.
OO tends to make much more sense once you analyze where the encapsulation should be.
"[OOP] just organized in a more systematic way." Managing complexity and abstraction is a main function of programming. How is having a simple, consistent, systematic way to organize complexity not an advantage?
I feel like this is one of my bigger gripes with OOP code bases: overemphasis on abstraction sometimes makes simple and efficient solutions harder to come by.
Semantic organization where methods are defined near the data (the recepie is on the flour package) is a benefit for sure, but it's just a preference.
I interview developers for positions all the time. Out of every 10 I interview, one can't write a basic "Hello, world" program that actually compiles, in the languages they list in their resume, on their own computers. About 7 or 8 more can't even close a database connection or file handle correctly. I can go a year and meet one, maybe two developers I would trust to write code in any methodology, in any programming language. And they usually have jobs they like and don't want to leave.
Projects don't fail because someone chose OO. They fail because most people claiming to be developers suck.
That said, I've stopped taking 'OO is bad' articles seriously long ago. Over 15 years of professional software development have sufficiently proven for me that there are many problems that fit the OO model perfectly, and would not benefit in any way from switching paradigms. Just like some (but decidedly less) other problems are a perfect fit for functional programming or procedural programming. There is no silver bullet in programming, what works best is entirely dictated by the problem, and problems come in infinite varieties.
If projects live or die based on competency, not methodology, then how is the existence of so many projects proof that "OO has worked", rather than proof that "Software Engineering is prone to bandwagons and hype"?
The existence of crappy programmers definitely does not suggest that all methodologies are equally good.
That said, I agree that OOP is anthropomorphism gone crazy.
After a while you realize that you're doing all this stuff to 'bad' students. This is where you really start to want some way to organize all the stuff you're doing to these bad students, as all the code you have dealing with them is sprinkled liberally through the codebase in conditionals in your procedures.
You'll start pulling out logic and things will start breaking. What you thought was less code is just crazy to manage.
The Gilded Rose kata illustrates the dangers of procedural code excellently.
http://craftsmanship.sv.cmu.edu/exercises/gilded-rose-kata
Sandi Metz has an excellent discussion on using OOP to improve the design of a codebase.
http://www.confreaks.com/videos/3358-railsconf-all-the-littl...
> There is just one little snag… The limits of analogies …When pushed too far, analogies break down.