14 ms·
Classes often aren't the simplest tool for the job
- cessor 5y agoThis is judging a fish on it's ability to fly. In other words, it is judging an object oriented program on its ability to resemble top-to-bottom sequential execution style programming, while ignoring that the language is commited to a different paradigm. Some of the criticism is really superficial: function createEmployee(firstName, lastName) { return { firstName: firstName, lastName: lastName, }; } OO-Programs have these weird factory functions called constructors that do exactly that. To me, it makes sense that such functions should share their living space with the class that they are creating. Where else would you like to put these functions? "Utils"? As for classes, namespaces and objects: as others have pointed out, this is an implementation detail. In Python, for example, classes are objects. Besides, classes and objects are functions (and the other way around) so it doesn't matter all that much in practice. What buggs me more is code like this: class SayHello { public static void toPerson(String name) { System.out.println("Hello " + name); } } Sure, comparing it to an empty Python file which just says "print" this looks like an aweful lot of code. However if the paradim is poorly understood, it will seem to be working against you: class Name { public Name (string name) { this.name = name; } public static void greet() { System.out.println("Hello " + this.name); } } The programming is different in that you create a declarative network of objects that then ... act. At this level, there is hardly any difference, except that the name has been introduced as a proper concept. Many people will argue that such value objects are expensive or inefficient (which they are) but if that cost hits you hard, you should be asking yourself why you picked a high level language to begin with. Yes, in such a paradigm a place is needed where the entry point lives, but I found it fundamentally useful. Whatever you call the class it: - Can read and encapsulate access to env variables - It can accept commandline arguments (as main(argv[]) indicates) - It's the place to setup and configure your (object)- dependency tree (what IoC/Dependency Injection-Containers are doing) - Or it can be the ultimate exception handler for messages deeper in your program, e.g. where you would start your main message pump / game loop, etc. The rest is ergonomics.
- savant_penguin 5y agoI only ever use classes to avoid global variables (mostly when using matplotlib animation) And even then I usually try to make a function of a function whenever possible. I find classes really hard to grasp (multiple inheritance/the syntax often sucks/diamond gotchas). Maybe I just don't understand them, but I've been able to get away without understanding classes for long enough
- deleted 5y ago[deleted]
- Swizec 5y agoI’d go as far as saying classes are never the simplest tool for the job. But maybe the past 10 years of functional-ish programming have broken me. There are lots of problem areas where objects fit nicely. Actor based programming is very elegant with classes and objects I think.
- cageface 5y agoThe two biggest problems with OO as it's traditionally used are: 1. Implementation inheritance creates brittle, tightly coupled and difficult to test code. 2. Mutable classes force you to structure data updates in a particular, inflexible way. In a world where you're sharing data across multiple clients, serializing and deserializing, using things like Redux to structure updates etc this creates a huge amount of internal friction.
- ipaddr 5y agoThere are things like static classes and functions that can co-exist.
- cageface 5y agoIsn’t a static class essentially just a namespace?
- Jensson 5y agoStatic classes still have constructors that gets invoked to create the static instance. Namespaces doesn't have constructors and instead each member of the namespace gets constructed separately.
- cageface 5y agoI guess it depends on the language but I've definitely had problems with codebases that depended heavily on static initializers. IMO it's usually better to explicitly bootstrap your code.
- donsupreme 5y agobut they are great for interviews with white board coding questions, which are great for getting a job nowaday!
- bcyn 5y agoHow so? Classes are extremely verbose IMO, which is the last thing I'd want in a white board interview.
- sirclueless 5y agoAs someone who occasionally watches competitive programming for fun (and competitive programming is basically competitive whiteboard interviewing) it gives me quite a bit of perverse pleasure that even in languages with great first-class class and object support the norm is to write functions that operate on global variables.
- deleted 5y ago[deleted]
- analognoise 5y agoWhere do you go to watch competitive programming? That sounds like it might help with Leetcode-style bullshit.
- sirclueless 5y agoI just follow a couple YouTubers who make videos about it. For example: William Lin - https://www.youtube.com/channel/UCKuDLsO0Wwef53qdHPjbU2Q https://www.youtube.com/channel/UCKuDLsO0Wwef53qdHPjbU2Q Errichto - https://www.youtube.com/channel/UCBr_Fu6q9iHYQCh13jmpbrg https://www.youtube.com/channel/UCBr_Fu6q9iHYQCh13jmpbrg
- ajkjk 5y agoThis article could really use an example of composition at the end to feel complete.
- adamzerner 5y agoI agree! I actually wanted to include one, but as I started to explain it it started getting too wordy, which made the post feel like it was more about composition vs inheritance than classes vs alternatives, thus distracting from the main point. I think this is mostly a limitation of me as a writer though rather than being an inherent thing. Ie. I think there's gotta be a way to give that example in a concise enough way where it doesn't distract from the main point.
- ajkjk 5y agoHm. I think your post would be a bit more compelling if you skipped examples of verbosity that are due to Java being shitty. JS would be simpler for the point? [nevermind the person who said that it's a low-quality post; it's just imperfect, not low-quality!] The compositional method of this is for each employee to have an `EmploymentStatus` member which contains information relevant to their employment status -- so `ContractorStatus` would contain hourly rate as a field; `FullTimeStatus` would contain salary as a field, etc. The class-based version of this puts the same attributes on the classes directly. Without additional fields that vary depending on the type, it doesn't make a lot of sense as an example. The `generatePayCheck` function doesn't demonstrate this, because it could just as easily be a standalone function that switches on the class or on an `employeeType: Contractor | PartTime | FullTime` field (and in languages that are less janky, this can be a discriminated union that includes additional fields in the type. I guess you can do that with Java via interfaces as well but it's not gonna be fun). A function that switches on the type, or an instance method on varying classes, or a discriminated union, or a composed attribute -- these are all isomorphic... up until you find yourself needing another type of variation at the same time. Perhaps you're modeling a university workforce and people are also either `Administration | Educator | Service` employees. Now you can't have 3x3=9 total classes, so you _need_ composition (for at least one of the type hierarchies, possibly both). The best place to use class hierarchies is when there is one 'canonical' inheritance hierarchy, rather than a bunch of ones that should be ideally compositional. What classes give you that unions don't is built-in methods, but you need those methods to be relevant to every type in the class hierarchy, so they really need to be intrinsic to the type of object you're talking about. For employees, `getPaycheck()` is probably a reasonable example of an actual class method for this reason.
- chrismorgan 5y agoThe JavaScript namespacing example is wonky: export default { email: "alice@example.com", login: function () { ... } }; By writing `export`, you’re showing you’re using modules; modules are namespacing, and this file potentially named currentUser.js can contain this instead: export let email = "alice@example.com"; export function login() { ... } That’s pure namespacing. And yes, export bindings are live, so you can use that like this (though you’re likely to surprise some people—but then, the whole style of the code was dubious in many languages anyway): import { email, login } from "./currentUser.js"; email = "bob@example.com"; login(); Back to the original example: speaking generally, `export default { … }` is a code smell that should normally be replaced with precise exports, which generally make life easier for tooling and consequently for humans. (As a consumer, the precise exports version requires only changing `import currentUser from "./currentUser.js"` to `import * as currentUser from "./currentUser.js"` in such cases.) Seeing so often such abuse of the default export, I have mused whether perhaps it was a misfeature, an injudicious appeasement and compatibility measure for older patterns.
- adamzerner 5y agoAh, you are right. I didn't realize this when I wrote it. Thanks for pointing it out.
- eyelidlessness 5y agoDefault export itself isn’t the misfeature. Mixed named/default exports is. There are several use cases/patterns where a single export is common (though because of the same concerns you raise, and others, I tend to use a single named export anyway).
- spankalee 5y agoThis is a pretty low-quality post. Of course you can pick on Java for shoving every feature into a class. That says nothing about classes in general. Printing: Java is verbose because it requires a class. Most languages with classes don't. The namespacing example in JS is weird because JS has namespaces as pointed out already, but again this case is only a problem in Java where you might use a class just cause everything has to be a class. Functions: same thing. Objects: not all languages have a separation between objects and class instances, those that do don't require you to use classes. :shrug: Object with shared behavior: yes, this is where classes are useful because this is practically the definition of classes. So the bottom line is use classes for the things they were designed for. Who knew?
- lmm 5y agoClasses turn out to conflate two or three different, unrelated concepts. Better languages split those things out so that you can use them separately. (Believe it or not, Java actually advanced best practices in this area: in C++ interfaces were just a "pattern", and many C++ fans were sceptical of the value of making a language-level distinction between interfaces and classes). When we say "A extends B" we mean a) an instance of A composes in an instance of B b) A implements the interface that B implicitly expresses c) this implementation, by default, delegates to the instance of A from a). Sometimes you really do want to do all three of those things. But often you want to do some subset of them instead. So the language should give you ways to do parts of that without doing all of that, and then "extends" should be at most lightweight syntax sugar over that.
- chmsky00 5y ago> Sometimes you really do want to do all three of those things. But often you want to do some subset of them instead. So the language should give you ways to do parts of that without doing all of that, and then "extends" should be at most lightweight syntax sugar over that. Sounds like you’re describing something like C. Leverage individual header files to implement a specific behavior rather than features concretized in a spec we don’t always want. Making everything a class, monoid, etc. feels like the programmer equivalent of making 9 Star Wars movies where each is comprised of lightsaber fights from open to close. That’s a cool still image but … really 9 movies huh?
- bedobi 5y agoAs a professional dev who has made a career out of working in oo languages and codebases, I agree, and it took me far too long to realize that when it comes to oo, the emperor has no clothes. To this day, oo advocates can't even agree on what oo even is or means. Apparently oo 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 oo 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 oo 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 oo 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.
- xupybd 5y agoClean architecture is worth a read. I like his description. "Object-Oriented programming imposes discipline on indirect transfer of control."
- bedobi 5y agoI've read all of Uncle Bob's books. IMO, Robert C Martin is to Computer Science what Nassim Taleb is to Economics and Finance and what Jared Diamond and Graham Hancock are to anthropology and archeology - they're psuedo-intellectual quacks pushing a bunch of impressive and authoritative sounding absolute bs with zero evidence behind it on unsuspecting victims. A lot of Robert C Martins pieces are just variations on his strong belief that ill-defined concepts like "craftsmanship" and "clean code" (which are basically just whatever his opinions are on any given day) is how to reduce defects and increase quality, not built-in safety and better tools, and if you think built-in safety and better tools are desirable, you're not a Real Programmer (tm). I'm not the only one who is skeptical of this toxic, holier-than-thou and dangerous attitude. Removing braces from if statements is a great example of another dangerous thing he advocates for no justifiable reason https://softwareengineering.stackexchange.com/questions/320247/has-can-anyone-challenge-uncle-bob-on-his-love-of-removing-useless-braces/320262 https://softwareengineering.stackexchange.com/questions/3202... Which caused the big OSX/iOS SSL bug in 2014, see https://www.imperialviolet.org/2014/02/22/applebug.html https://www.imperialviolet.org/2014/02/22/applebug.html This link and thread on hackernews is good too https://news.ycombinator.com/item?id=15440848 https://news.ycombinator.com/item?id=15440848 The current state of software safety discussion resembles the state of medical safety discussion 2, 3 decades ago (yeah, software is really really behind time). Back then, too, the thoughts on medical safety also were divided into 2 schools: the professionalism and the process oriented. The former school argues more or less what Uncle Bob argues: blame the damned and * who made the mistakes; be more careful, damn it. But of course, that stupidity fell out of favor. After all, when mistakes kill, people are serious about it. After a while, serious people realize that blaming and clamoring for care backfires big time. That's when they applied, you know, science and statistic to safety. So, tools are upgraded: better color coded medicine boxes, for example, or checklists in surgery. But it's more. They figured out what trainings and processes provide high impacts and do them rigorously. Nurses are taught (I am not kidding you) how to question doctors when weird things happen; identity verification (ever notice why nurses ask your birthday like a thousand times a day?) got extremely serious; etc. My take: give it a few more years, and software, too, probably will follow the same path. We needs more data, though.
- btown 5y agoA comment in the Stack Overflow post that the OP links to led me to https://en.wikipedia.org/wiki/Liskov_substitution_principle https://en.wikipedia.org/wiki/Liskov_substitution_principle which seems to be far more useful a concept to know than the OP: > Substitutability is a principle in object-oriented programming stating that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., an object of type T may be substituted with any object of a subtype S) without altering any of the desirable properties of the program (correctness, task performed, etc.). If your Ugly Duckling doesn't quack like a duck, or is only raised by a duck but is not one itself (a has-a relationship, ergo composition!)... it probably shouldn't be a subclass of Duck. But if it does those things, and you gain more than you lose from the simplicity of passing around simple objects (okay, the analogy has completely frayed here), it should!
- CBLT 5y agoThe interview[0] with Barbara Liskov about the principle is also really interesting. > Although it may seem a little strange these days, in those days it was a long way from the east coast to the west coast, and of course we had no conference calls in those days too. That whole business about object-oriented programming was developing on the west coast and on the east coast we were mostly working on data abstraction, and the two worlds were kind of separated. So I knew the name, but we didn’t run into each other at conferences and there wasn’t much crosstalk going on. > In the 1980s, I was asked to give a keynote at OOPSLA, which I think it was maybe the second OOPSLA. It hadn’t been in existence very long. So I decided that this was a good opportunity to learn about what was going on in object- oriented languages. So I started reading all the papers and I discovered that hierarchy was being used for two different purposes. One was simply inheritance. So I have a class, it implements something, I can build a subclass, I can borrow all that implementation, change it however I want, add a few extra methods, change the representation. Whatever I want to do, I just sort of borrow the code and keep working on it. > The other way it was being used was for type hierarchy. So the idea was that the superclass would define a supertype, and then a subclass would extend this to become a subtype. I thought this idea of type hierarchy was very interesting, but I also felt that they didn’t understand it very well. I remember reading papers in which it was clear they were very confused about it, because one in particular that I remember said that a stack and a queue were both subtypes of one another. This is clearly not true because if you wrote a program that expected a stack and you got a queue instead, you would be very surprised by its behavior. The difference between LIFO and FIFO is a big deal. > This led me to start thinking about “What does it really mean to have a supertype and subtype?” And I came up with a rule, an informal rule which I presented in my keynote at OOPSLA which simply said that a subtype should behave like a supertype as far as you can tell by using the supertype methods. So it wasn’t that it couldn’t behave differently. It’s just that as long as you limited your interaction with its objects to the supertype methods, you would get the behavior you expected. [...] > Meanwhile I was working on distributed computing, I was particularly interested in viewstamped replication and some of the other work that was going on in my group at the time, and I wasn’t really thinking about this until sometime in the ’90s when I got an email from someone who said, “Can you tell me if this is the correct meaning of the Liskov substitution principle?” So that was the first time I had any idea [laughs] that there was such a thing, that this name had developed. [0] https://amturing.acm.org/pdf/LiskovTuringTranscript.pdf https://amturing.acm.org/pdf/LiskovTuringTranscript.pdf page 29
- throw_m239339 5y agoJust because Java implements object oriented programming in a rigid and clunky way doesn't make your Java anecdote a general principle. This isn't a very good blogpost. But I'm glad HN is back about arguing about petty programming stuff nonless rather than partisan politics.
- dexwiz 5y agoClasses are nice when used in conjunction with a framework. Most developers realize that the Car that extends Vehicle from CS 101 is a limited pattern that isn’t very useful. And OO languages often force developers to declare classes when they really need namespaces or first class functions. But if you are working with something like Spring Beans or React Components, classes serve as a decent unit within a framework. They help guide developers to factor their code in blocks of roughly similar size and scope. They help organize a codebase into something explorable for new members. Like chapters in a book, they don’t have to all be the same size, and may vary between books/codebases. That being said, most languages overload classes to be record/objects and namespace modules. I like functions, and generally agree with the post. But when all you have is functions calling functions it’s not much better than spaghetti in large projects. You can still make a mess with classes, but they give a conventional way to delineate layers.
- boredtofears 5y agoI think where these decisions gets hazy to me, especially in a language like Javascript, is the language is chock full of enough features that I often find the decision to make class/function/object to be somewhat negligible when the purpose is as trivial as the examples provided. You can make those factory functions nearly as concise as the class examples if you wanted: const createEmployee = (firstName, lastName) => ({ firstName, lastName }) const createContractor = (firstName, lastName) => ({ ...createEmployee(firstName, lastName), //overloaded propeties here }) const createFullTimeEmployee = (firstName, lastName) => ({ ...createEmployee(firstName, lastName), //overloaded propeties here }) I've always thought more in terms of consumption - who will be using this code and how will it be used? My probably unscientific approach being a dutiful Typescript worker bee has been to take the general approach: - Try to just use a function, especially when your output will only be consumed one way - If the domain of the logic that you are working must encapsulate multiple ways to be consumed and there isn't a clear primary use case, and much of the logic and state should be shared between functions, consider a class I don't find the end result to be significant for anything other than code cleanliness/readability. Admittedly I've never really had language features themselves be performance bottlenecks doing mostly frontend web work. This may be too specific of an observation of Javascript itself to be a broad opinion though. I'd love to hear someone else's opinion as my experience is not from a compsci background.
- adamzerner 5y ago> I don't find the end result to be significant for anything other than code cleanliness/readability. Agreed, I think. But isn't readability a very important goal? "Programs must be written for people to read, and only incidentally for machines to execute." > I often find the decision to make class/function/object to be somewhat negligible when the purpose is as trivial as the examples provided. Yeah I agree with that. The reason I chose trivial examples was just to make it easier to understand.
- DeathArrow 5y agoI hate how Java and C# forces every function to be a method.(i.e. member of a class)