9 ms·
I've said this multiple times over multiple years and I'll say it again just because so many green developers get misled. Object oriented programming is good.
by 3pt14159 6y ago
I've said this multiple times over multiple years and I'll say it again just because so many green developers get misled.
Object oriented programming is good. Not in absolutely every situation, but for most business applications it's better than the alternatives. The reason it's good is that it is productive. Objects map neatly to records, which pushes global state persistence and all the hairiness of locks to a good RDMS like Postgres. It's naturally normalized, but can be denormalized without much work, and in exchange for bundling state with functions you get easier introspection which is especially helpful in debugging.
These conversations are better done with examples. Say you want to build a social network around videos, like YouTube. Say a user can block another user and, thus, stop them from commenting on their videos.
Would you rather type out this:
user.blocked_by? other_user
Or this
is_blocked(user, other_user)
When doing the check? Personally, seeing the the is_blocked? method on a user makes it easy for me to understand. Who has blocked whom is more natural to talk about when there is a primary object subjecting the other object to a test. For all the anti-OO stuff out there, it all boils down to the same thing in the end. For business logic, your functions need to know so much about the data that they are operating on that it's pointless to try to avoid bundling the state. I'm never going to pass a cheeseburger into my is_blocked function so why on earth would I avoid bundling state and operation together? It's like a map of a city trying to avoid listing shops since "maps should be about roads and geography, not shops." A pointless dogma that doesn't actually help programs get built. Most successful startups use OOP and there is a clear reason for that: It's more productive.
Now, do I use OOP when I'm doing data science stuff? Mostly no. I don't need it there. But where it works, it's magic.
- joe_the_user 6y agoObject oriented programming is good. Not in absolutely every situation, but for most business applications it's better than the alternatives. The thing about OO is that as far as I can tell, it's natural. Whatever OO is criticized, people point out that the standard OO example is "car" but the typical OO object in practice is "network credentials permissions". So what? The usefulness of OO is exactly that allows, partially, some totally fungly thing like "network credentials permissions" to be treated, sort of, like a thing, we sort of, understand, a fricken car. And further thing is, the fp thing seems to be something like "we don't do objects at all but we do really elaborate Turing complete types, please don't ask us what the difference is, you wouldn't understand. Don't ask us whether types have private data. Especially don't ask if integers have private bits..."
- Arch-TK 6y agoI think you're falling into a thinking trap. And I think there's probably a name for it but I don't remember it. The point I will make is that "a car is an object you can do things with" is actually probably not how you think about a car. Personally I think of my car and the "objects" around me as merely tools I can use to achieve goals. If I need to go to the shop I don't think to myself: I need to go to the car object and call the door_open(DRIVER_SIDE) method and then call the add_driver(self) method and then the door_close(DRIVER_SIDE) method and then the turn_key() method... When I need to go to the shop I come up with a shopping list (or more likely get my girlfriend to send it to me) and then I think about my destination and how I can get there. I think of using my car to go there. So simply put, I think the object model has no relation to how we actually think about objects around us. The object model (At least alan kay's variant) may be a good description of how humans talk to each other, and I think it has its place for sure, but it's also nothing like the object model which gets sold to people these days. To me "network credentials permissions" sounds like just some data which describes the permissions required to access another piece of data called a network credential. That's how I think about it, I am not sure it's accurate to say that my method of thinking about it is any more or less natural than thinking of "network credentials permissions" as a thing but personally I would say that considering "network credentials permissions" to be a thing seems just outright wrong.
- joe_the_user 6y agoThe point I will make is that "a car is an object you can do things with" is actually probably not how you think about a car. I can't imagine how this isn't exactly me or someone else thinks about a car. I might then think about what I accomplish with a car. And sure, "car" and "bicycle" are "transportation means I can steer" and feed "transport means" and "destination" into an algorithm and arrive a location. Sure, "transport means" is a higher logical level "car" but both of these go into human thought processes. If I need to go to the shop I don't think to myself: I need to go to the car object and call the door_open(DRIVER_SIDE) method and then call the add_driver(self) method and then the door_close(DRIVER_SIDE) method and then the turn_key() method... The only reason you don't think you think about that, is because your self-conception apparently delegates the process to your reflexes. But if you have explain to someone how this happens, you reflexively dive and give some English equivalent to your narative above.
- waheoo 6y agoI find the hardest part about OO is that a lot of people don't know what good OO looks like. Most new developers fall into the pragmatic trap and shy away from refactoring because of startup culture cargo culting. Good OO requires large refractors on the regular, and with a good design, refractors like this are low cost high return, but failing to do them results in mud eventually. I used to shy away from it "because nobody can do OO so it's better if we just don't". Writing everything in procedural and functional is quite productive, but it too requires adherence to rules and avoidance of mutable state. Once I realised, there are no shortcuts, you just have to learn what works and what gets you into trouble later I tried OO again, only now I use it sparingly and tend to mix procedural, functional, and OO depend on the task at hand. One of the amazing things about PHP is that you can do this easily and clearly. Things like go, java, or even JS these days tend to force you into one way or the other. Earlier on I aimed for plain objects / structs for information, grouped state passing but I realised these often lead to smells that are hard to clean up. Now I limit their usage to the edges and opt for objects instead. Passing implementations along with the state really cleans things up in coherent ways. I think one of the scariest things about good OO is that it looks like bad code to me. It looks "not dry". It looks, untidy, lots of state sharing and member variables to track things. From a functional perspective that stuff gets you into trouble. But from OO perspective its what works. The trick is keeping classes small enough to be aware of all the member variables when reading any member function. You shouldn't get blindsided by random state changes elsewhere, that is a sign there is more than one thing being done by that class.
- jordan_curve 6y ago> Earlier on I aimed for plain objects / structs for information, grouped state passing I’m a relatively junior dev and I believe this is a pattern I use a lot. Would you be willing to elaborate on what you mean by it and what some common pitfalls are?
- waheoo 6y agoIt's probably my default "get things done" style of code so I don't want to suggest it's something you want to actively avoid. Plain objects have the benefit of OO in that you have a bunch of normal friendly objects that map well to a database record. But they tend to be treated more like variables. They work well for groups of functions that need a lot of the same parameters but you don't quite want to lock in an object boundary. Often I'll start with a plain object or plain functions, then once I can see the seams I'll form objects. Sometimes you can see these up front as obvious, I still try to hold off. The longer you wait the better and more concise your objects will be. You want you objects to look more like behaviors than records. (Though there is heavy cross over, plain objects / structs make good records and you can easily consume them in objects.) The big pitfalls: Mutation, avoid it where possible, mutation is a sign you need an object. Immutable structs are bliss to use, they're easy to reason about. Mutable structs are basically impossible to reason about, never know what's in there. Using an object to pass a single variable to a function. Using an object to pass a single variable / subset to a sub function call. Basically, try to terminate plain objects at boundary edges like you would nullable variables. There isn't anything wrong with passing an object around for one variable, its just a bit messy and creates a bigger dependency than required. It makes it hard to refactor your plain object out later because you can't tell what is actually depended on down in the stack.
- Arch-TK 6y ago> Objects map neatly to records This is plain wrong. The problem of mapping records (presumably you mean database records which are actually tuples but lets call them records) in a relational database to objects in an object hierarchy is the source of the "object-relational mismatch" wherein the fundamentally graph-based structure of an object model is intrinsically incompatible with the related sets-of-tuples structure of a relational database. To try to solve this problem, an inordinate amount of time has been put over many years to develop various ORMs (Object-Relational Mappers) which try to hide this intrinsic incompatibility under many layers of abstraction. A whole new industry came out of the simple fact that people who had been lead into thinking that OOP was the only solution to any problem wanted to use relational databases to back their objects. These ORMs all come with innumerable caveats and shortcomings. Their abstractions constantly leak and their performance is a constant firefight between making code idomatic OOP and making code actually perform in a reasonable way. To claim that objects map neatly to database records is completely ignorant of even the history of how ORMs came about. Your comparison of `user.blocked_by? other_user` to `is_blocked(user, other_user)` is a complete red herring anyway. Nothing stops a non-OO type system from letting people specify that a function applies to a type such that you can write `foo.is_blocked_by(bar)`. Nothing also stops you from writing code more explicitly and clearly such as: `foo in user_blocklist_of(bar)` or even mixing both concepts and writing `foo in bar.blocklist()`. In summary, these are all questions of syntax and style and have nothing to do with OOP. > The reason it's good is that it is productive. This implies that somehow all other methods of programming are less productive? When I have the rare occasion where I need to write some kind of web application I reach for three things: Flask, SQLAlchemy Core and sqlite3. I design the database then design the application around it. By actually directly interacting with sqlite3 using a query generator like SQLAlchemy Core I get control over and the ability to use the features of my database of choice. I am no longer stuck trying to fix query performance issues by randomly mangling an object model until the resulting database interactions act like I want them to. This to me seems far more productive than any of my experiences dealing with ORM performance issues and abstraction leaks. > Who has blocked whom is more natural to talk about when there is a primary object subjecting the other object to a test. I think there's nothing about this particular example which puts particular emphasis on either side of the block as the "subject" and the other side as the "object". - a blocks b - b is blocked by a There's nothing about either of these which stands out as the correct mental model. In reality it's not a subject-object relationship it's just a relationship between two subjects. In a database this would be stored as a table containing a 1:many relationship between user and user. By attaching this relationship strictly to one side of the relationship you've actually ignored the case where it may be useful take the perspective of the other side of the relationship. If you end up factoring that into your object model you will presumably end up having two functions: `.is_blocked_by` and `.is_blocking`. These will likely have to have two separate implementations (although I'm sure you can get the ORM to handle that for you). (Also, don't forget the performance impact that you're likely dealing with WHOLE user records at once and relying on some lazy loading specification which you probably defined too loosely to be useful to avoid loading the entire "other user" from the database just to check if you're blocking them.) I think now it should be clear that the single separate function approach is actually far more representative of the reality of the relationship.
- geocar 6y agoI'm not sure I understand you. is_blocked(user, other_user) seems like it’s still object-oriented to me. Oh sure it has terrible syntax, but we’re still designing our application with encapsulation and if this is CL and “is-blocked” is a generic then it’s how you would write this in an CLOS-style OOP — it would just be written (is-blocked user other-user) On the other hand: other_user.id in user.blocks is what I would imagine a non-OOP version might look like, because it’s not dynamic and not encapsulated. If we were to write it: in(other_user.id, user.blocks) or: (in (other-user :id) (user :blocks)) I also still think these functions are better than the object-oriented ones: they’re faster, and it’s clear to the reader if you can get the ids without the object (say by pushing this into your query) you will save a lot of runtime and programmer-time. I also think if you make decisions based on what is popular (e.g. for startups), instead of actually thinking about it, you are subscribing to dogma. Maybe sometimes not-thinking is good, but it seems possible that what you think of as a “start up” has largely only existed in the short time period that object languages were in vogue. Why do you think this is causal? Do you think if go (for example) becomes popular we won’t have startups any more?
- kstenerud 6y agoThey also force you to reach into the implementation's data structure directly (the "blocks" container), whereas the message-oriented one allows you to refactor without having to visit every call site and also potentially need to rework any other accesses that are doing more than just checking for membership. Message passing allows much more fine-grained access controls to the implementation, and in many cases that's highly desirable, especially as the implementation gets more complex and the purposes of accessing the data more specific, and excess coupling more problematic.
- bionhoward 6y agoknow any good examples of fine grained access control in message passing systems? sounds compelling for a current project
- kstenerud 6y ago
- continuational 6y agoWhat you seem to like is infix syntax and overloading on the first argument. I think this is really the most valuable part of OOP; it makes code read in the order that functions are applied, which is both natural and enables type driven autocompletion. What bothers me about OOP is inheritance, subtyping and the emphasis on mutable state, which greatly complicate call chains, the type system and temporal reasoning.
- martamorena943 6y agoYes, but honestly that is super easy to avoid. We code in Java and pretty much all objects are @Immutable, inheritance is only used when its the right tool for the problem (it really rarely is) and subtyping is solely done through interfaces. I think the main reason what people get wrong about OOP is that they tend to think everything needs to be models with inheritence, which is completely wrong. Inheritence is BAD! The rule should be: 1. Avoid inheritance 2. Use interfaces instead 3. If interfaces are not enough, use composition 4. If composition is not enough go through all other similar design patterns that are not based on inheritance. 5. If you really need inheritance, be very careful about which function you make non-final and non-private.
- carlmr 6y agoThat sounds like you like functional programming with associated functions from OOP and not much else from the GoF book of nightmares. I mean Erlang is an actual OOP language according to the original creator of OOP's concept of message passing between objects. But each object internally is quite functional. It's just the Java crowd's deep inheritance enterprise code that makes every developer who survived that want to vomit.
- cryptos 6y agoI think most interpretations of OOP are too specific. Interpretations are often about classes, inheritance, polymorphism and stuff like that. But the original idea of OOP was more about message passing, so that you tell an abject what you would like it to do, but have no further influence of how it performs the request (if at all).
- winrid 6y agoIn the second example, I would do instead: if (user.blockingId == otherUser)... Even if there is some scary logic you would argue you need to hide, I'd go further to say you have a UserLocks module so you can do: if (UserLocks.getUserBlockedId(user) == .... Kind of a hard example to work with because I'm not sure why a user would block exactly one other user, but yep.
- fulafel 6y agoI think you have your own definition of OO going here. Maybe a better name for it would be db data centric programming? You can use a class based object system to do it, but it's not needed or necessarily especially well suited for it. I agree positional arguments can be bad in a case like this. OTOH you can just look at .is_blocked_by? as infix syntax here which just works in many languages. The syntax problem in the example can also be solved eg by using named arguments. (But to reiterate record.function(x) syntax doesn't need an OO system either, or even language support if you have macros)
- stiray 6y agoThis is one point. The other point is hiding in real life usage. User is derived from human, that from lifeform, that from cell,dnk,(...) each of those is derived from molecule, that from atom and then goes into quantum physics parts. And is_blocked is implemented on higgs bozom, which has method is_blocked and in case of error throws it from there. Surely is_blocked is also implemented on all other levels, each adding just a small information to the result. /s OO is elegant. Even C++ operators overloading is great (MFC CString is a nice example). The problem is that people who think what they are doing based on what they have learned in school or some "pattern" (with not enough mileage - I am talking about 15+ years that you have spent doing OO monstrosities before you reached the enlightenment) don't understand that you don't need higgs bosom in user object. And that someone searching for an issue in this architectural beauty represented by user object is going to start scratching his head when is_blocked is called and catches higgs_bosom::exception. Even more when encounters god_factory. Once someone figures out that there is a need to understand the universe to be able to handle a bug in otherwise simple is_blocked function the obvious and rational reaction is screw OO. But the problem is not in OO but rather in people using it to express their philosophy of life that needs to be infinitely ad absurdum reusable,... I have seen this everywhere, from most prominent libraries for C++ to java where this is seen at its worse. I dont dislike OO. I dislike people making lasagnas. Unscrupulously patternizing everything and everywhere for the sake of doing it. And I havent even started with patterns, refactoring once you figure out that scope has changed,... will rather use an example: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris... At the end. I love OO. But I prefer a language where you cant inherit. Cant throw exception. As this prevents people from over complicating the code for no reason and helps me to not read their whole epic architectural poetry to use a simple function. Face it. Most programmers cant code. They are terrible at doing it. And the more powerful tool that they get, the more they will abuse it and not even know they are doing it in most cases.
- alkonaut 6y ago> The reason it's good is that it is productive. I'm going to half-agree but rephrase: it's a kind of programming where even a novice programmer can iteratively approach the correct solution by adding more and more code. That's good from a management point of view: you can always feel like you are getting closer and closer to the goal because you get more and more code and fulfil more and more of the requirements. But I wouldn't go so far as saying this is productive, especially not in the long term. For one thing, you'll end up with a lot of OO code which is the worse thing for productivity. > Would you rather type out this: Or this That's not the important distinction between OO and functional. The important distinction here is: when you call the function, is it defined by user? Is it defined on some more abstract base_class which also applies to "groups" and "corporations"? If you want to change it, what do you have to do, and which types and behaviors do you affect when you change it? Subclass polymorphism is is almost invariably a net negative in my experience. The syntax of OO is great. We realized this especially since it grew to fame at the same time as the clever IDE with autocompletion. But your example would look like the first in e.g. Rust, which isn't an OO language by any stretch of the imagination.
- watwut 6y ago> But I wouldn't go so far as saying this is productive, especially not in the long term. For one thing, you'll end up with a lot of OO code which is the worse thing for productivity. I really dont see why either of these would be the case.
- davidkunz 6y agoThere are also languages like Nim which have a method call syntax, that means: myMethod(myObj, myParams) == myObj.myMethod(myParams)
- yellowapple 6y agoRelevantly, the same thing applies in Python, which is probably where Nim got it (though I don't recall if Python actually lets you interchange them - only that the thing on the left side of the dot is the first argument of the method). Perl also does this (but with -> rather than .), and also allows interchanging the syntax.
- nybble41 6y agoLua is another one. The following expressions are all equivalent: object:method(arguments...) object.method(object, arguments...) object["method"](object, arguments...) Lua objects are tables (associative arrays), the dot and colon operators are forms of table lookup, and methods are ordinary functions stored in the table (or more commonly its associated meta-table) which happen to take the object as their first argument. The colon operator, which can only be used as part of a function call, passes its left-hand operand to the function along with the other arguments.
- skocznymroczny 6y agoD also has this feature. Uniform Function Call Syntax.
- junippor 6y agoYour argument for why OO is good is that you prefer one syntax over the other? The examples you gave are just syntax and have nothing to do with OO. You could imagine a language that isn't OO but where you could define the . operator to dispatch certain functions with the same syntax as OO.
- nesarkvechnep 6y agoOOP maps neatly to records is RDB mostly in CRUD applications. The reason is ORMs build SQL queries but SQL does set operations. OOP does not map neatly to set operations and that’s one of the reasons ORMs would always generate inefficient complex queries.
- turminal 6y ago> Most successful startups use OOP and there is a clear reason for that: It's more productive. Evaluating programming techniques based on what startups use doesn't sound like a good idea to me because 1) Codebases in startups usually aren't the best examples of quality and cleanliness and 2) optimizing for productivity above anything else has the potential of failing in the long term in a lot of settings (including startups).
- RedNifre 6y agoSyntax is separate from programming paradigms. Some languages allow you to write any function infix style, like user `blocked_by` other_user
- AlchemistCamp 6y ago> For business logic, your functions need to know so much about the data that they are operating on that it's pointless to try to avoid bundling the state. This hasn't been my experience at all working with Elixir. A typical Phoenix app will have hundreds to thousands of funtions related to business logic and none of them bundle state. It works fine and debugging is much easier than it was for me previously with JS or Ruby because of the pure functions.
- pjmlp 6y agoAda, CLOS among others use the second syntax for OOP.
- infinite8s 6y agoIt's clear the GP has never worked with multiple-dispatch languages.
- PRINCE_PD 6y agoWho has blocked whom is more natural to talk about when there is a primary object subjecting the other object to a test It is more natural when you're already used to OOP, but like any language, each have its syntax which feels very natural for its speakers. Comparing the structure of a similar sentence in Japanese and English, would you affirm to the japaneses "the english sentense is more natural"? I don't think they would agree. So your point about syntax is irrelevant. If you practice a language and love it, you'll embrace its syntax and it will feel very natural. Objects map neatly to records Well, some data structure maps even more neatly to records, no? {:id 1, :name 'bob'} Most successful startups use OOP and there is a clear reason for that: It's more productive I prefer not to waste time arguing with this, but programming is not about "successful startups".
- dkersten 6y agoThe former isn't how most OOP languages do it and those two options aren't the only two options. Personally, as someone who mainly uses Clojure, I would rather type: (blocked-by? user other-user) With that said, it really doesn't matter which of these options I have to type, because as usual, the syntax is less important than the semantics and all of these options can work with or without OOP. Now, OOP isn't bad per se, its just a tool. It depends on how you use it. I do buy into the Clojure notion though that there is a problem with most OOP languages and that is that they conflate multiple concerns and features into objects. Give me the features to mix and match as I please, instead. That is, if I want to keep my data and my functions together, great, but if I don't, also great. If I want to do polymorphism with objects, great, if I want to do it without, also great. Let me choose, based on the problem I'm solving. I tend to favour a functional-first approach, but will use OOP as it makes sense or when it makes the API easier. To me, immutable state (where possible) is more important than an avoidance of OOP. I also write a lot of C++ and I won't shy away from creating some classes where it makes sense.
- dragonwriter 6y ago> Objects map neatly to records FP data items (also, often called “objects”, but with different meaning) do so at least as well as OOP objects. Better in many ways. > which pushes global state persistence and all the hairiness of locks to a good RDMS like Postgres. OOP obscures whether an entity is an immutable data item extracted from an RDBMS or a mutable local thing or a mutable thing which mutating also effects an external resource like a database, but there is nothing about FP which prevents, compared to OOP, pushing “global state persistence and all the hairiness of locks to a good RDMS like Postgres.” In fact, the opposite; there's a reason no one talks about a Functional-Relational Impedance Mismatch. > It's naturally normalized No, OOP is not “naturally normalized”.