15 ms·
The controller pattern is awful, and other OO heresy (2013)
- agurk 3y agoCould we get a (2013) in the title please.
- Cthulhu_ 3y agoMakes me wonder if / what changed in OOP land since then; from what I gathered it's pretty stagnant.
- jghn 3y agoDepends on how you view OOP. For instance "The Repeated Deaths of OOP" (2015) [1] notes how the definition of OOP has been morphing over time to match current realities. [1] https://loup-vaillant.fr/articles/deaths-of-oop https://loup-vaillant.fr/articles/deaths-of-oop
- prewett 3y agoMy personal opinion is the problem people are solving moved from what used to be called "desktop applications", for which OOP is a good solution, to other problems. Also, the advantages of HTML/JS (cross-platform, ease of distributing updates) is so great that native apps have fallen out of favor. On the server side and for things like ML/AI, OOP is less helpful, hence the rise of FP. Unfortunately, the people developing web frameworks apparently did not have much experience with native GUI libraries, which all work more or less the same, and they decided that a functional programming style with React was the way to go. So now you have a soup of React hooks and JS callbacks that makes code as readable as something with goto everywhere. But each individual page is rarely complex enough for this to make maintenance more than slower / more tedious than necessary. OOP is old-fashioned now. React is the way.
- indigo945 3y agoLast pop quiz: what makes Python an object-oriented language? Ah, hm. It can’t be classes, or I’d tell you you’re wrong. So what is it? [...] self? No, that’s not a keyword or anything; it’s just the de facto standard name for the first argument. So what makes self work? That’s close enough, really. The answer is descriptors, which are basically “the things that make self work”. From another blog post on the same site [1]: A descriptor is just an object; there’s nothing inherently special about it. Like many powerful Python features, they’re surprisingly simple. To get the descriptor behavior, only three conditions need to be met: 1. You have a new-style class. [...] It therefore follows logically that Python code using old-style classes is not object-oriented, and that Python was not an object-oriented language prior to the introduction of descriptors. [1]: https://eev.ee/blog/2012/05/23/python-faq-descriptors/ https://eev.ee/blog/2012/05/23/python-faq-descriptors/
- martius 3y agoIn the first case, she's mentioning descriptors to make the same point as for "this" with javascript: you can bind a set of related data to a function. Exposing "descriptors" is a latter thing, but their default implementation was already a thing before they were visible to python users.
- williamcotton 3y agoYou can write object-oriented code in C, but no matter what tricks you do with storing function pointers in structs, you still have to pass the struct itself as an explicit argument. A sprinkle of clang blocks and Block_copy on top of your structs and you’re on your way!
- Inviz 3y agoIs there something like that in gcc?
- williamcotton 3y agoThere is not! There are those nested functions but they’re missing the ability to close over state and store it on the heap.
- ivan_gammel 3y agoIt is interesting that Java is mentioned there as a language without first-class functions. Maybe, but from programmer‘s perspective it kinda does and they are encapsulated in «namespaces»: they are static methods of classes and with ˋimport staticˋ they do look like first-class functions.
- rat9988 3y agoI'm not sure how it is related to being first class. You cannot use a function as a variable.
- AnimalMuppet 3y agoWell... you can define an interface, and have a class that implements the interface, and pass an instance of that class as a variable. That's not quite "passing a function as a variable", but it's kind of close if you squint.
- ivan_gammel 3y agoWrong answer since March 2014.
- vips7L 3y agoYou can. You just need to know what type of function it is first. Runnable r = () -> doSomething(); Supplier<String> a = () -> “hello world”;
- ivan_gammel 3y agoWhat do you mean? Since Java 8 you can assign a function to a variable, pass it as a parameter etc. Lambdas in Java are not pure, since they are converted to objects by compiler, but on user level they have all attributes of first class functions.
- deleted 3y ago[deleted]
- whimsicalism 3y ago2013 in the title please, also the tone of this post is a bit condescending
- thatxliner 3y ago1. Oops, I’ll keep that in mind next time I post a link. 2. This isn’t my blog, but just something I would like more people to know about.
- DeathArrow 3y ago>Object-oriented programming is about objects: bundles of state and behavior. That means hiding state, and having it spread throughout the app, in places that are hard to find, which is error prone and leads to complexity. And monstrosities like FactoryFactoryFactory [0] and Kingdom of nouns. [0] https://factoryfactoryfactory.net/ https://factoryfactoryfactory.net/ [1] http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom...
- usrbinbash 3y agoI don't see how. Golang has first class support for bundling state and behavior: Receiver-Functions, often called methods. This means exactly 2 things; a) That for a type `Foo` I can do this: f := Foo{} f.funcThatUsesFoo() instead of this: f := Foo() funcThatTakesFooAsFirstArgument(f) and b) that Foo implements all interfaces whos method signatures it's receiver functions satisfy. It doesn't mean that I now have to suddenly drown my code in over-architectured patterns just to satisfy some OOP sense of code-aesthetic. It doesn't mean that I have to try and hide state from code in the same module. It doesn't mean that I have to bundle things that could be free-standing functions into some type just because. It doesn't mean I have to make any of the mistakes OOP popularized over the years.
- deleted 3y ago[deleted]
- gumby 3y agoThis ten year old essay is still strong. It makes some points that are worth repeating > I have a hypothesis: this pattern is so common for the simple reason that Java doesn’t have first-class functions. She’s picking on Java deservedly because Gosling made what in retrospect was a mistake to go so all-in on classes, and because it became such an important pedagogical and deployment language (and nobody will shoot at you if you’re unsuccessful). Say what you will about C++ but it took a different path, not only changing its name from “C with Classes” but making classes a tool for manipulating the type system (and not the only one) and supporting the true benefit of a class system, generics. Edit: I changed an incorrect "He" pronoun to "She". Thanks to quickthrower2 for pointing out this embarassing blunder.
- quickthrower2 3y agoNitpick: She not he
- boredumb 3y agoHere's my hot take. Most patterns are incredibly awful and abused because people prefer to solve architectural related problems in code and produce "elegant" solutions over delivering features. Here's my pattern that I use instead of a MVC/MVVM/MVP/etc/etc: I have a handler function to choose what url and routine I need and it calls some functions and methods and then renders some json / templates out some html. I think a good rule of OOP is to try to write everything out as functionally pure as possible and refactor into classes when you see data AND functionality blocked together that you can bottle up into an object. To start ranting, I think a lot of the OOP insanity you see in .net/java/etc is from dogmatic approaches to unit testing over there and the common examples of how to do it involving the mocking and interfacing that end up obfuscating the actual production code.
- koromak 3y agoYou can kinda argue whatever you want when it comes to this stuff. It doesn't really matter. Is the code readable? Does it not make me angry to look at? Yes? Then its good enough. I don't care if the object has one method that probably should be a function. I can still read it fine.
- boredumb 3y agoI agree. The problem these OO patterns tend to introduce is unneeded levels of abstraction that all boil down to making the code difficult to read without three wide screen monitors.
- paddim8 3y agoNot inherently. You get unneeded levels of abstractions if you add them yourself. You can follow these patterns without doing everything at once.
- nathias 3y agoOOP was a mistake rooted in platonism
- jononomo 3y agoThis is the truth. The entire OOP paradigm is just a profound misunderstanding of how the world works.
- discreteevent 3y agoWell Simula was developed specifically to model real world problems. And when they had invented it they found it much more useful than that which had preceded it. OO is not supposed to model nature. It's not string theory or whatever other incomplete-yet-closer-to-reality-than-oo model that physisicts use.
- goatlover 3y agoBut what about how humans think? We make use of universals in our language all the time. And sometimes that fits a problem domain well, like making a GUI or running a simulation of things.
- nathias 3y agowe don't really think in terms of OOP, OOP is an abstraction and systematization of inheritance, which we do use to think sometimes, but as its abstracted it becomes untennable to use ... there are relations between things that can be mapped well with inheritance, but thats a problem, we don't really want code to represent the thing we're coding, code needs to be more dynamic easier to read, change and move around, etc. representation should be between the end result of the code and the thing we're trying to represent
- c_crank 3y agoIt's a good thing the greatest minds of programming have worked hard to solve this problem by giving us the even more platonic and rigid functional programming.
- 3y ago
- cheschire 3y ago> The app is the object, and the various URL handlers are its state. There were a lot of assertions in this article that rubbed me wrong, but this one was particularly egregious. Handlers are behavior. They handle something. This feels like the kind of article I would've written early in my career when I was getting really clever with stuff and starting to be able to question the way I understood the world of programming.
- weego 3y agoThe article even displays clearly that the URL handlers are not state. They're the contracts that help negotiate state.
- robertlagrant 3y agoI think it's saying that the state of the app object itself is the configuration of the linkage between paths, verbs and handler functions, and that's why it's worth making an app an object.
- musingsole 3y agoThe Handlers implement behavior, sure. But from the perspective of the `app` object, the handler function objects are state. You can tell, because `app` is an instance of the Flask class and the Handlers are added to the instance by way of a function call. So, Handlers are functions that "handle something", but they are also part of the app object's state, not its behavior.
- kiviuq 3y agoBy that definition an app could never be stateless ?
- BeefWellington 3y agoThat's a very oversimplified view of handlers. An alternate view might be: Handlers are functions that are part of the app object's state, which alters the app's behavior. In the majority (possibly overwhelming majority) of the cases this is true. The purpose of Handlers and similar Publisher/Subscriber patterns is effectively to change application behavior when they are configured.
- xg15 3y agoI wonder if at some point, we could train an LLM to churn out hot takes about why this one design pattern is awesome but that other design pattern is horrible and why you need to die in a fire if you use MVC but everyone should be using MVVCP instead but do stay away from MVPVC or your life will be wasted. Then we could train a second LLM to read the first LLM's articles and write flaming rebuttals and have the first LLM write rebuttals to the rebuttals. And if everything works, we could finally automate the entire debate away and go on writing code.
- AnimalMuppet 3y agoRight, but what paradigm do you use to write those LLMs?
- xg15 3y agoDefinitely MVVM. I'll die on that hill.
- AnonymousPlanet 3y agoWe could train our vacuums to utter snarky comments and eat popcorn while they watch these LLMs. Then we can go back to do something productive like gardening work.
- weinzierl 3y agoCurrent LLMs are trained on internet data which is mostly hot takes including the ones you mentioned. I'm not sure there is much room for improvement and what you suggested will probably work well with existing LLMs without any specialized training.
- Semaphor 3y agoI did MVC and MVVM with GPT4: https://pastebin.com/mtUR12cA https://pastebin.com/mtUR12cA Limited to one paragraph each, though. I’m sure it could write a whole blog post ;)
- narag 3y agoThe many "you're wrong if you believe that" made me remember the anecdote of Alan Kay attending some conference about OOP and saying that it was wrong, that he invented OOP and it was not like that. IIRC, Kay's vision is that OOP is about messages. I have my own pet theory, of course it must be wrong too, but if I may, only as food for thought: The core usefulness of OOP is usability. A language is a matter of connecting thoughts through a mental model. Subject, verb, predicate. Object, method, parameters. That's mostly it. The rest are implementation details and lots of bikeshedding.
- arethuza 3y agoWhat about things like CLOS where you have multi-methods and dispatching is done based on the type of all arguments?
- mistrial9 3y agocombine a type system with dispatch logic, using abstractions .. it is very clear for some engineering applications and/or tinkertoys. Many people can get the basic ideas with thirty minutes of introduction.
- narag 3y agoI haven't used it, so not sure if it fits the pattern, but the question for me is: is it useful, time-saving, more usable? does it make the task more clear for the programmer that uses it?
- jghn 3y agoIt depends. It is a fundamentally different branch of the OOP family tree than what most people are used to seeing. Enough so that I've seen people declare it to *not be OOP*. So if you stumble across the model having only seen the style of OOP popularized by C++ and later Java, no. You will probably *not* find it to be liberating. The idea is that you have classes which model state. And then you have generics that model functionality. And you define methods which provide an implementation of the generic for a class. But it's more flexible than what you see in Java because such a method can be easily added after the fact as it's not intrinsically part of the class' definition. If you're familiar at all with Typeclasses in Haskell & Scala, personally I find those to be similar enough to get the gist. Likewise Dylan and R's S4 objects are modeled after CLOS' structure.
- iambateman 3y agoMeanwhile, in busytown…millions of developers will wake up on Monday morning, sit down to their work and make something using the best tools they know. If it works for you and your team, it works.
- weatherlight 3y agoSuffer what you must, but some of us in busy town dream of a better world. Exploring better patterns "using the best tools" we have.
- prewett 3y agoSome of us would prefer spend our days with WhatsApp server's elegance rather than than the Facebook server's ugliness. When you have to write a compiler for a poorly designed dynamic language just to get decent performance because there isn't a feasible plan to migrate to something that truly solves the problem you have, you can't help but think that maybe things could be simpler.
- jmull 3y ago> It’s nothing. There’s no way to describe it without sounding like a blowhard. It’s not “a controller for some URL space”, because that’s what the class is. An instance of it is utterly meaningless! IMO, this is being almost willfully obtuse and pointlessly pedantic. For one thing, the instance, not the class, is the thing most clearly described as the controller in this case. It's true that there's no need to have instances for this static logic, but it's also true that some routing behaviors have their own dynamic data (like caches or database connections, etc.) So a point of just always using instances is that you don't need to know or care externally or ahead of time whether or not such data exists (and to provide something better than singletons or worse things to manage it when it does.) I'm not a big fan of this form, BTW, though it's not on OO grounds. It's a code and logic organization thing. When I've seen this in real life it turns into spaghetti code of little objects connected in a tangle. I think it goes down like this: people start by encapsulating little steps of processing, I guess because they image they are independent. At this point it's OK -- a sequence of objects connected in a chain of references matching the processing steps. Then they realize they aren't independent, but instead of unwinding the chain, they add more objects -- to hold bits of common context -- and connect them here and there. The spaghetti is starting to tangle. (Mistakes are always made at this point -- e.g. there's ends up no coherent way to update this common data as things change.) The thing grows 10x and these kinds of things are repeated ten times and you have a big ball of spaghetti. Oh, and someone introduced a DI framework to solve these, which helps a little, but also introduces its own complications.
- dawnofdusk 3y agoIs the problem here not simply that Python does not have a convenient way to namespace functions except a class declaration? The `module` keyword in e.g., Julia or Rust seems like what's desired to avoid this phenomenon of "a class which is just a bundle of static methods".
- jononomo 3y agoPython uses modules to namespace functions.
- dawnofdusk 3y agoYes but modules are very unergonomic for some of the use cases listed in the article, with their dependence on `__init__.py` and imports being wonky.
- jononomo 3y agoYou don't have to use __init__.py -- you can just create any python file and it will be a module. And I'm not sure why you think that's unergonomic. If you want a bunch of functions to share the same namespace, just put them in the same file. The name of the file becomes your module name.
- kgeist 3y agoI actually like "dumb" service classes with 1 method. Usually that method may require tens of dependencies to function (db connection, authorization logic, etc.), which in turn have their own dependencies recursively, and your choices are: 1) use global variables/functions for dependencies, 2) pass all dependencies as arguments to your function, 3) inject dependencies in the constructor of a service class/functor. Global state is a no-no in multithreaded code and is poorly manageable (side effects and all), while forcing a client of your function to pass all required dependencies manually as arguments is cumbersome and basically leaks implementation details (hard to refactor). So for me, service classes are the best option. It's kind of equivalent to a closure (object fields are basically captured variables), just written using the familiar OOP syntax (OOP syntax != OOP semantics). In the context of web, I tend to prefer stateless architectures (because they are easily scalable and less error-prone), and coupling behavior with state often goes in the way, in my experience. So my favorite architecture is a network of service classes whose dependencies are injected in their constructors and which are basically "smart" functions (not "objects"), they have no state, and they operate on domain objects which have no logic other than basic self-validation during mutation (to uphold invariants). I know it's a heated topic (rich model vs. anemic) but I've seen projects naturally, through evolution, end up being more anemic than rich. Pure OOP, where state and behavior are entangled, feels very cumbersome to me, it's harder to refactor when requirements change every day, and having too much state is pretty error-prone. YMMV.
- moron4hire 3y ago"A closure is just a method where the instance members are implicit. A method is just a closure where the closed over state is explicit." I agree with you about service classes. I think one of the things that service classes do well that regular functional code does not is indicate to the consuming developer which parameters are meant to be provided by the environment and are largely static, and which are meant to vary per invocation. Lisp does this instead with dynamic scope. But dynamically scoped dependencies felt too much like global variables to me. The service class instance has its scope bound to it and it won't change. But dynamically scoped functions always felt like the ground could get ripped out from under me at any time.
- zero_shift 3y ago
- yurishimo 3y agoOne nitpick from the article, PHP does have first class support for functions in the language. Functions are available in a few different ways: - Normal function declaration in global and "module"/namespace scope. - Anonymous functions that can also be assigned to variables (except in a few cases, like object properties) - Anything extending the Closure class The last one is obviously a nod at what is going on under the hood, but as far as the developer writing the code is concerned, it doesn't matter. A library even exists to explicitly provide some functional programming functions under a namespace for easy inclusion with other projects. There is no requirement that all functions must be defined in a namespace, as evidenced by the first 10 years of crappy PHP code littered with functions in the global namespace! https://github.com/lstrojny/functional-php https://github.com/lstrojny/functional-php Edit: formatting
- colanderman 3y agoSo does C++ for that matter. `std::function` has existed since C++11 (which does predate the essay).
- thurn 3y agoOOP is a tool like any other, and it has real costs and benefits. The original idea of being able to easily add new common APIs via inheritance is very powerful and widely applicable. In fact both Haskell and Rust wound up with a similar mechanism to inheritance (the 'deriving' annotation) to reduce the pain of problems like "I don't want to have to write code to define equality for every struct" which are hard for the purely-functional approach to solve.
- zaphar 3y agoNeither Haskell or Rust use inheritance for this though. They use Typeclasses or Traits respectively. Those have the nice property that they solve the specific problem of satisfying an interface for something without the accompanying problems of inheritance. Derive as an example doesn't use inheritance it uses code generation precisely because Rust wants to avoid inheritance. The key insight of eevee's article is that OO has succeeded mostly because bundling state and behavior together is a really useful way to structure your code and is the thing that you should leverage the most out of object oriented languages.
- bccdee 3y ago`derive` macros would not be possible to imitate with inheritance. `derive(PartialEq)`, for instance, examines all the fields of a struct definition and generates a function which compares them all. Using inheritance, you could inherit from a parent `PartialEquivalence` class, but if you added any new fields, you'd have to write your new implementation of the `==` operation by hand to accommodate them. Macros have a much longer history in purely functional languages (in particular, lisps) than they do in procedural or OO languages. So you could argue that this problem is actually much easier to solve from a functional approach.
- TrianguloY 3y agoI don't see this mentioned, but on python a file can be considered a singleton class. If you come from java, this may help you write better python code. No need to encapsulate things on a class! The file is the singleton itself!
- goatlover 3y agoAnd how much do the Python libraries make use of that as opposed to defining their own classes for you to use?
- jononomo 3y agoPython libraries make use of modules (i.e., Python files) all the time.
- not_jules 3y agoIt's how the majority of the stdlib is written. Construction of objects with the stdlib is pretty limited to things that allocate os resources (files, sockets, etc with obvious state full ness and destruction behavior) unless you're already operating on objects (dict views, iterators). The exceptions are those which were ported (unittest was a clone of java 's framework incl method names).
- seanthemon 3y agoThat's useful!
- quickthrower2 3y agoShe seems quite deeply knowledgeable about a lot of different languages which is impressive. Or much research was done. I just don’t have that much stuff stick especially for languages I don’t use every day.
- weare138 3y agoI have a hypothesis: this pattern is so common for the simple reason that Java doesn’t have first-class functions. ----- I would put C++ and PHP in second and third place, and they have the same flaw. PHP supports first-class functions.
- jononomo 3y agoIn my opinion you're not really doing object-oriented programming unless each object is also a separate process. This is why the best object-oriented programming uses functional programming languages and the actor model (i.e., Erlang). Just having state and methods, but without having an associated isolated process means you end up in situations in which multiple processes may fiddling with the same objects and all your encapsulation of data just gets you in a twist. It's only when you commit fully to functional programming and have immutable state that you truly unlock the power of object-oriented programming.
- AnimalMuppet 3y ago> without having an associated isolated process means you end up in situations in which multiple processes may fiddling with the same objects and all your encapsulation of data just gets you in a twist. 1. Don't do that. Don't have multiple threads messing with the same data. 2. If you have to do that, use a mutex/semaphore/guarded region. So far, so conventional. There's nothing about OOP in that. The difference with OOP is that only member functions can access that data, so you have a strictly limited number of places you have to think about protecting. OOP makes that aspect easier. Each class just protects its own data. Of course, you still have to worry about "deadly embrace", and by putting the semaphores in the class, it can make that aspect harder to reason about. > It's only when you commit fully to functional programming and have immutable state that you truly unlock the power of object-oriented programming. Um, no. Almost all real programs have mutable state, and the state has to live somewhere. I work in embedded systems. There's often a lot of persistent state, and, worse, shared mutable persistent state. FP isn't going to make that go away; it's just going to distribute it differently. But if I have to reason about the shared mutable state, then a distribution that pretends it isn't there doesn't make that easier. (Pure functions, on the other hand, do help.) I mean, look, FP is absolutely right that shared mutable state is evil. Avoid it... if you can. (And think harder about whether you can.) But sometimes the very nature of the problem is shared mutable state, and when that happens, you need tools that help you deal with it, not tools that say "don't do that".
- jononomo 3y ago
- AugustoCAS 3y agoI recently escaped an organization that had a couple of apps built using this approach everywhere but for the opossite reason stated in the article. The team wanted to write code in a functional way in Java because _FP is better than OO_. Most (if not all) classes had a single method and implemented BiFunction. The app was wired with Spring... if you had the bad luck of using Spring you can get the idea of how ugly this was. All method invocations were `apply`s and navigating the code was as easy as finding a needle in a haystack in the middle of the night. A good amount of the wonderful features of a tool like IntelliJ couldn't be used. My rule of thumb is to follow the grain of the language / framework / library as to produce as little surprise to other devs.
- a_wild_dandan 3y agoYour org avoided OOP...by using the quintessential OOP language? That is, ah, certainly a perfect way to fail at functional programming.
- c_crank 3y agoI now want to work at a shop that uses fully imperative Haskell.
- BeefWellington 3y agoWhat's amazing about this is there are other very good functional JVM languages they could be doing this with...
- theknocker 3y ago[dead]
- Mikhail_Edoshin 3y agoThe author of the article also writes about “good classes.” She criticizes two specific mistakes one can make in Python classes (and she is right), but not the OOP itself.
- elzbardico 3y ago[flagged]
- bob1029 3y ago> Stop writing stupid classes. This is really it. It's not a game of all-OO, zero-OO or strictly following a pattern you saw on YouTube that one time. It's all about nuance and minimizing the # of moving parts until each is really serving you some value. I am in the process of ripping out a bunch of class bloat in a product iteration. Our typical pile of model class POCO spam for "one thing": Entity.cs --The "official" view EntityController.cs EntityView.cs --Another common view that became popular over time (???) EntityTableRow.cs --gotta have that 1:1 sql model, amirite? EntityViewForTypeA.cs --what was wrong with the common one :( EntityViewForTypeB.cs --And again... ... Ultimately, every time we wanted a "view" of the data, we wound up creating another type to enshrine this and then tried to figure out how many holes we could more-or-less hammer it through. Every one of these has some cranky mapping layer too (NxM if you want to get crazy). The alternative pattern I am working with is to directly query my database in the exact place the data is required and to use anonymous / scalar return types like so: //Some function that produces a HTML partial view for a webapp //View w/ join that would otherwise require a new POCO type to communicate. var myEntityView = await sql.QueryFirstAsync(@$" SELECT e.Id AS Id, ep.Name as Name FROM Entities e, EntityProperties ep WHERE e.Id = ep.EntityId AND e.Id = @Id", ...); //Direct usage of anonymous result type. return $@" <h3>Entity: <a href='Entities/{myEntityView.Id}'>{myEntityView.Name}</a> </h3>"; In the above, there are zero POCOs or "messenger" types. Just a raw query right where it needs to be taken, without any extra ceremony or implications for other code sites. I actually don't have a single type in the new codebase that is a POCO, other than schema tracking models (which are still useful for interpolating into queries to enforce magic strings). Effectively, the only domain data types are 1:1 with the SQL schema now.
- vkou 3y ago>This is really it. It's not a game of all-OO, zero-OO or strictly following a pattern you saw on YouTube that one time. It's all about nuance and minimizing the # of moving parts until each is really serving you some value. Minimizing the number of moving parts shouldn't be your goal. Testability of the moving/complicated parts should be. I don't really care about how you break up your code, but it should be trivially testable, at both the unit, and the integration test level. OOP makes it at least possible for less experienced developers to accomplish both of those things. It's nice that you got rid of all the indirection and all the nearly-pass-through interfaces in your code sample, but how would you test it? In a unit test? In an integration test? Are you going to need to dial out to a real, live SQL database to do so? For every test? Will you be re-using the database across multiple tests, and thus, require all your tests to avoid object collisions? Paying a startup cost for every test you run? Reusing it for some tests? Who's going to maintain that reused, QA database? Who's going to deal with it when some clown pollutes it with garbage? These are all solvable problems, they have multiple approaches to solving them, with varying tradeoffs, but they are tradeoffs. 'Simpler-to-write' code is usually not the tradeoff you want to optimize for. You write code once, you test it thousands of times.
- bedobi 3y agoAs a professional dev who has made a career out of working in oop languages and codebases, it took me far too long to realize that when it comes to oop, the emperor has no clothes. To this day, oop advocates can't even agree on what oop even is or means. Apparently oop as envisioned by Alan Kay was supposed to work like cells in the body that pass messages between each other and take actions independently. Why? Who knows! It was never really explained why literally one of the most complex systems imaginable, one that we still really have very little idea how it even works, should be the model for what could and should probably be a lot simpler. Today's modern oop languages are probably very far from what Kay envisioned (whatever that was), but it's remains unclear why classes and objects are "better" than the alternatives. And before anyone goes and comments aksully code organization blabla like yes but code organization can be great or shit in oop or fp or procedural codebases, it has nothing to do with the "paradigm". Let alone that the entrenched, canonical, idiomatic coding styles of most modern oop languages encourage state, mutability, nulls, exceptions and god knows how many trivially preventable entire classes of errors. Granted, most have now started to come around and are adopting more fp features and ideas every year, but still. Don't get me wrong, writing programs like cells in the body that pass messages between each other and take actions independently is an interesting idea which deserves pursuing, if nothing else but to satisfy our curiosity and seeing to what if anything it's applicable and suited. (and even if the answer turns out to be "nothing", we've still learned something!) But going from there to making strong claims about it being a more or less universally superior paradigm for computing and writing code, with little to zero evidence, that's a huge, huge stretch. To the degree Erlang and Actors work, I think that's kind of a happy coincidence, and not due to any rigorous work on Alan Kay's part.
- Akronymus 3y ago> Why? Who knows! It was never really explained why literally one of the most complex systems imaginable, one that we still really have very little idea how it even works, should be the model for what could and should probably be a lot simpler. From my understanding, it is that every object has a very limited set of functionalities it manages. And that a goal is for each object to know as little as possible of the outside world. And along with that, to not bind to specific objects/classes but to bind to specific messages (Including multiple dispatch)
- jononomo 3y agoIn my experience, people who really like object-oriented programming just haven't yet gotten the hang of functional programming.
- acscott 3y agoA little bird told me that it was easier to break up big projects into classes and farm them out than in other languages at the time. The `protected` keyword for example prevented the other developer(s) from modifying that part of the object, creating safety.
- prewett 3y agoMaybe I've been ruined by early exposure to C++, back when C++ was the new hotness, but I find encapsulation and even the namespacing aspect to be helpful. Encapsulation is obviously helpful, but it also comes in handy for some algorithms that are complex enough to require several functions, but which share a lot of state. If I recall correctly, Voronoi tesselation is one of these. Even though an "tesselate" is an action, putting it in a class just makes it easier to think about things. Otherwise as a user of the function I have to first create the shared data object, then know to call the three helper functions. Better would be a high-level tesselate() call, but then that call needs to do that. Now you've just pushed the complexity down to the next maintainer, who needs to figure out how all this soup of functions from all the various algorithms and whatnot that have collected over the years relate to each other. With an object, the newbie maintainer at least knows what the topic of the functions is.
- lamontcg 3y agoWell, yes, but no, but yes. A counter example of a good bag of function is every languages Math static class. Also it is quite viable to have a bag of data and multiple different classes which have algorithm which operate on that data. Those algorithms should not be trivial (but there is no good definition of what trivial is or isn't). If you toss all those algorithms into the bag-o-data class you will wind up with a massive class which changes under too many circumstances and clearly violates separation of concerns. If you have a three line function that should clearly not go into its own class. If you have a dozen methods that implement an algorithm on a class, and the private methods don't need to be shared amongst other algorithms, and perhaps you have some private state dedicated to just that algorithm that doesn't make sense to have in the data-holding class, then you should probably extract that into a stand alone class. Where is the actual dividing line? I don't know. Wrestling with it all right now with some code that I've been writing for the past several weeks. It doesn't help that I don't yet have a second algorithm that hits this data, so there's no good dividing line for me between what should be private to the algorithm and what should be public hanging off the data. It is not anywhere near as clear as the author of this blog makes it out to be though. And the algorithm I'm implementing is a Job, which inherits from BackgroundJob as well. The horrors. (I do at least have three different BackgroundJobs now which all need common task handling and cancellation/timeout kinds of logic wrapped around them so that's a bit more clear what that abstract base class should look like).
- dxuh 3y agoI find it funny that people that know almost nothing about C++ talk about it so much. Function pointers are not the only way to treat functions as values, as is implied in the article. Lambdas exist and have existed in 2013 as well (and are nowadays the canonical way to have something like first-class functions). And what does this mean? Quick: off the top of your head, what makes JavaScript an object-oriented language? If you thought “what? it’s not!” then there is no hope for you and you should go back to C++. Ironically it's the C++ programmers that have pretty much collectively understood that classes are for maintaining invariants, which this person is almost about to understand in this article (I hope they did by now).
- ddtaylor 3y agoI am surprised that an article this length never mentions "message passing" once.
- slotrans 3y ago> I have a hypothesis: this pattern is so common for the simple reason that Java doesn’t have first-class functions. Exactly correct. See also "Execution in The Kingdom of Nouns"