29 ms·
I Don't Use Classes
- beastman82 7y agoclasses are OOP. FP doesn't need them. The end.
- dana321 7y agoClasses are everywhere, but are used in different ways or hidden. There are types of classes that are hidden and cannot be re-instantiated. To me, those are modules and the main execution environment. Whenever i've been caught out when coding its always one or both of these things, so i always wrap these in a class otherwise when it comes to reusing something multiple times you're caught short. So, i always use classes for base functionality. But i don't throw them around like sugar candy.
- nickthemagicman 7y agoIn school they taught classes and inheritance like it was the biggest thing since sliced bread. I feel like they're but one tool, and necessary in limited cases. For example I recently encountered an API that had to intake 100+ key/value pairs and manipulate these values, and send them to various places. I tried to do the mappings and data manipulations and everything in one long piece of code functionally but it led to some serious spaghetti. When I separated things out into classes, it was much cleaner code, with a more well defined domains. It was definitely not perfect but ended up being much cleaner. This is a simple example but classes can be useful. It's arguable that the API could have been better written and that's true but sometimes you have to work with what you're given. Otherwise I would venture to say most code doesn't require classes.
- LessDmesg 7y agoBy "I separated things out into classes", do you mean that you used the whole triad of inheritance + polymorphism + encapsulation?
- simion314 7y agoDo you need all 3 of them to have a class? In my experience classes are data+ methods that manipulate that data. Like a Vector class would have a rotate,normalize etc methods that return a new Vector.
- nickthemagicman 7y agoExactly. Clusters of parameters and functions that manipulate those parameters all specific to a 'domian' is really what a class is for. Inheritance/ Polymorphism is just more machinery to do that with. Functionality like mapping the keys to new names or uppercasing certain specific values or sending the data to certain places. You could have the functions and parameters all in one long piece of code, or you could put them into domains which makes dealing with it, reading it, understanding it, and working with it easier, in my opinion.
- jerf 7y agoThat's a question of definition. There are many who will passionately insist that yes, you absolutely need all of them. I hypothesize but can't prove that these are people simply regurgitating what they were taught in a classroom and who have not yet left Java/C++ to see what else is out there and what else can work. It is perfectly feasible to be educated that way and then spend your entire career in Java or C++ and come to conceptualize everything else as "that crazy minority stuff that isn't like Real Programming", and, in their defense, they are at least correct about the "minority" part. It's easy to forget if you read HN a lot, but inheritance + polymorphism + encapsulation remain the massively dominant paradigm, especially if you assume most C# is still basically written that way despite supporting other paradigms. Personally I take the loosest possibly view, mostly just "associating data and methods to operate on that data fairly tightly together, where they all get passed around as a unit". Some ability to have "x.y()" result in multiple possible implementations of "y" is necessary, or you just have a weird way of spelling function calls. That's about all I'll require to call something "OO" nowadays.
- simion314 7y ago
- agumonkey 7y ago> In school they taught classes and inheritance like it was the biggest thing since sliced bread. they even had to dance around how to have a main entry point in class only languages like java the early 2k were really religion blinded around a class god, without teaching the real values and uses of classes in the context of problem solving
- tannhaeuser 7y agoNot even 2k, it was a thing in 1990s already. I remember a student coming in to the company I worked with around 1993/4. His thesis was about O/R binding frameworks and approaches for relational DBs. Generations of developers have drank the "impedance mismatch" and other OOP pseudo science koolaid since.
- gigatexal 7y agoA whole article and no code examples. Fail.
- optimuspaul 7y agoI was thinking the same. His arguments seem thin at best and suggest a misunderstanding of what OOP really means.
- crimsonalucard 7y agoI would argue that a heavy practitioner of OOP, and one who does not see the extreme downsides of using that paradigm is actually the person who misunderstands Programming in general and what OOP is in that context. Say if you practiced procedural programming in a very heavy way and typed functional programming in a major way along with OOP then you would have a better picture. If you practiced OOP exclusively for the majority of your career I would argue your view point is flawed and biased. Over the years, as a programmer with a lot of heavy experience in all of the paradigms mentioned above, I have found that OOP is in fact not an apples and oranges comparison to other programming styles but is in fact a highly flawed paradigm.
- crimsonalucard 7y agoEven if he did present code examples I doubt it would change your mind. I think OOP people should be aware that there is significant backlash against OOP among smart people who are very familiar with the paradigm and they should take the time to understand why. The division is real and given the invention of languages like GO and RUST that do not have classes I would think really hard about the topic.
- dith3r 7y agoGO is simple decomposition of OOP, class was split into structure (with composition) and interface(with multiple inheritance without implicit definition what interfaces can can coop with structure). Go only restricted that you cannot define virtual methods.
- chadlavi 7y agoI don't either. Almost always an arrow function (or just regular old function) does what I need. Plus, React Hooks!
- INTPnerd 7y agoThe main benefit of classes is as a way to define custom types. Otherwise you are stuck with the types built into the language, which are not designed to help ensure correct program state, logic, and behavior. When you use classes in this way, I call it type oriented programming (TOP). Why would I compare classes to types? Just pass in the initial value at construction time, define the "operations" on that type by creating methods for your class, and define which other types those operations work with by setting the types of parameters those methods work with. Make the class immutable, always returning a new instance with the new updated value passed into the constructor. Why did I mention these types helping the program be more correct? This should be used as the primary form of contracts. But these contracts are very simple to use, you just specify the relevant type in the parameters and return values, and you are done defining the contract for any given method. For example, let's say a method should only work with integers larger than zero, instead of either accepting an Int or an UnsignedInt, which would allow 0, you could define your own class called PositiveInt. It would be designed to throw an exception if you pass anything smaller than 0 into the constructor. Then instead of writing code inside the method that makes sure the user of the method is following the contract, you just specify PositiveInt as the parameter type. If the contract is violated, the exception will be thrown as early as possible, before the method is even called, helping programmers catch the original source of the problem. This also makes your code more readable, because you can see exactly what each method accepts and returns just by looking at the method signature. When you start thinking this way, you will notice many core types are missing from the language, that should have been there from the beginning. Fortunately you now know how to build them yourself.
- xamuel 7y agoI use C structs in the same way. I think it's fine to use classes for this purpose. The problem with classes is that certain people go crazy with inheritance and polymorphism, things which sound smart on paper but almost always lead to horrific unmaintainable code in practice.
- INTPnerd 7y agoThis is a good point. Right now Kotlin is my favorite language. By default classes and methods are closed for inheritance, and therefore for polymorphism. I like the way this is the default, but you can override it when/if necessary. It also has data classes, which in my opinion is what most classes should be. I think when a class is not be a good fit for data classes, this is a code smell.
- frou_dh 7y agoSurely the ultimate example of this phenomenon is OCaml. It's wearing OOP right on its sleeve in the name of the language, but its users inevitably say "Oh, we never (or hardly ever) use that part".
- osener 7y agoAbsolutely. Objects in OCaml is useful for structural typing, but I reach for classes way less often than I initially thought I would (read: never). It turns out modules are indeed a better solution for, well, modularization. OCaml is multi-paradigm in the most broad sense of the word I've experienced, just with functional and immutable as the default rather than the other way around. And it has a pretty great OOP implementation too. Suggested reading for those wondering why the community deemed the O in OCaml mostly unnecessary and how rest of the language helps achieve the same goals in a different/better way: https://discuss.ocaml.org/t/objects-use-cases-in-ocaml/2282 https://discuss.ocaml.org/t/objects-use-cases-in-ocaml/2282
- sweeneyrod 7y agoI think the lesson from OCaml is that 90% of the value of OOP comes from things like packaging datatypes with functions that use them and separating implementations from interfaces which can be implemented with non-OOP things like modules. From an OCaml perspective, the main thing that you can only get from OOP and not elsewhere is open recursion, which is useful but only in certain niches.
- BiteCode_dev 7y ago"As I’ve mentioned above, I like to use modules that expose groups of functions. These functions accept state and other dependencies. These modules tend to look like what my colleague Drew describes as the functional module pattern." I dislike that. When I see a class, I expect it exists because there is a central state around which the methods revolve, and are meant to expose or change it. When I see a function, I expect it to be side effect free. Sure you can use any paradigm to make any program, but I use paradigms, just like I use design patterns, to communicate intent. Solving the problem is half the battle. In a way, coding style is documentation. I think we should build boring API, and only use cleverness to find solutions, or make the API easier to use and more elegant. I worked once on a code base that broke a lot of implicit expectations. Like, it could add objects in a registry when you instantiated some classes, get_stuff could set other stuff, do_stuff would not do stuff but actually_do_stuff would, etc. It was a nightmare to work with, because anything could happen anywhere. You had to look at the code every single time and keep the whole system in your head, which had 900k lines of highly technical code.
- rclayton 7y agoThis is a great reply. If you don’t use classes to define behavior for objects that manage their own state, there’s no point in using OOP.
- gchamonlive 7y agoI don't think you actually do state management in OOP, only manage a single level of abstractions. In java, inheiretance is directional in a single way, but if you instantiate a singleton component, you can end up affecting the state of every other class that uses that same component. This is not wrong per se, it is a property of OOP in itself. If this rubs you the wrong way, possibly you should be working in purely, monadic functions.
- BiteCode_dev 7y agoIn think singleton has been labelled as anti-pattern in OO for a long time. Having a single reference is useful, but using instantiation as a way to get it has proven a bad design decision.
- fdghfg 7y agoclasses only encapsulate context. nothing else. you don't have to use them if you don't want to.
- skocznymroczny 7y agoUsing classes doesn't necessitate using inheritance and polymorphism. Whenever I'm programming in a non-OOP language, I still gravitate towards class-like code. From my perspective, there isn't much different between: doStuff(&fooStruct, ...) vs. fooClass.doStuff(...)
- djsumdog 7y agoThe core difference is in testing. With the later, you need to get your class into the right state to preform the test. You might even need a mock or two. With the non-OOP approach, you set the entire state of the function and see the results.
- Ididntdothis 7y agoIs that even true? In the end it’s all the same code. You can write classes that need a lot of context but the same can happen with functions.
- jayd16 7y agoIf the method needs just as much state in a struct as you would on a class then its equivalent.
- Double_a_92 7y ago> you set the entire state And how would you easily create that state?
- Ididntdothis 7y agoYou create a mock :)
- aidenn0 7y agoYou are wrong. You need to create just as much state in fooStruct as fooClass in the example GP gives.
- brobinson 7y ago
- asdfman123 7y agoFinally, a man who truly strives to live in a classless society.
- partiallypro 7y agoIt was a miss opportunity not to call the article "I'm Classless" or "Class is cancelled"
- taneq 7y agoAnd as always, first thing a classless society does is to find a way to emulate classes.
- asdfman123 7y agoYou win the internet.
- Koshkin 7y agoClassless society (or programming) is like cell-less biology.
- aidenn0 7y agoWhile I like classes, the first 4[1] programming languages I used did not have classes. The first dialect of the first language I learned didn't even have functions! 1: Basic, Logo, C, Pascal. Logo and Basic I learned 2 fairly different dialects of even (Atari/Apple and GW/Quick respectively).
- asurty 7y agoThis reminds me of a podcast I listened to recently about creativity. Maybe its not about the idiomatic question of whether classes are good or bad but more about the act of placing the limitation that helps drive the masterpiece? https://www.npr.org/transcripts/719557642 https://www.npr.org/transcripts/719557642
- INTPnerd 7y agoIn Kotlin classes and methods are closed by default, making avoiding inheritance and polymorphism the default. It also makes you mark each variable as mutable or immutable, by declaring it with a var or val, making you think more about (and enforce) state being mutable or immutable, thus avoiding a lot of state problems. It also has a concept called data classes, which works really well for most well designed classes. I now consider the case where a class is not a good candidate for data classes to be a code smell. Most classes should be a good candidate for data classes or else you are usually using classes in a way that causes more problems than it solves. The thing is, it doesn't prevent you from creating classes or methods that are open for inheritance, nor does it prevent you from allowing mutable state, but it does use the defaults and the language design to encourage limitations that are usually good. Perhaps they could take these concepts a step further by making classes data classes by default, having a keyword for it to not be a data class.
- kalimoxto 7y ago(Apologies to the Flaming Lips, and I think the pronunciation shift you have to do at the end makes it funnier) I know a girl who wrote some code she'll make a linked list she'll add some nodes But she don't use classes and she don't use C she don't use Haskell or any of these she uses assembly assembly assembly
- Pamar 7y agoQuestion: The author writes (as an example of his problems relying on classes): Well, some tables when updating need to do more than just “update” a record, such as insert a log into a side table. I do not understand exactly if there is a problem here, or anyway I do not understand what the Functional alternative would be. Assuming this is a problem, I suppose we have three choices: a) You have to provide implementation for two distinct methods: - Update(...) and LoggedUpdate(...) (the latter calls the former and then logs the result, most probably). b) You decide that update() implicitly logs maybe based on a global configuration or similar mechanism. c) You provide a boolean flag as parte of the method signature update(log). What would be the proper Functional approach here? (Caveat - I have no experience with Functional programming)
- tomstuart 7y agoYou’re always going to be on one side of the expression problem [0]: either your operations live with your datatype (classes), which makes it inconvenient to add a new operation, or they live somewhere else (“functional modules”), which makes it inconvenient to add a new case to the datatype. This choice is probably informed by your expectations of how often you intend to do either of those things, but there’s going to be inconvenience regardless. The big benefit of objects, and therefore classes (or something like them), is polymorphism via dynamic dispatch; it seems a shame to throw the baby out with the bathwater because you don’t like inheritance or mutable state, both of which are independent of the choice to use classes. [0] https://en.wikipedia.org/wiki/Expression_problem https://en.wikipedia.org/wiki/Expression_problem
- bcrosby95 7y agoThere are languages that solve the expression problem, such as Clojure. But they aren't very popular.
- Scarbutt 7y agoIn some languages runtime polymorphism is not tied to types.
- keymone 7y agohttps://clojure.org/reference/multimethods https://clojure.org/reference/multimethods
- CuriousSkeptic 7y agoI’m not sure dynamic dispatch is that much of a killer feature. Take C# it default to static dispatch of method and you tend to get very far without explicitly making things virtual. Event when using interfaces many times it’s seems possible to arrange things such that parametric polymorphism would be able to use a static type. There are some boiling proposals for future versions that will help with exactly this, and indeed some of the latest changes to the languages have included allowing more static dispatching in polymorphic constructs like disposal and enumeration. I’m beginning to suspect that dynamic dispatch could be entirely dropped without losing to much expressiveness. Have no experience with Rust but isn’t that kind of the conclusion from that community?
- jeffadotio 7y agoI like classes. But you must understand your code, even before you write it. If the syntax that you use (the author is using TS, and JS/TS classes are syntactic sugar) matches your mental model of the program then I would say that you are choosing the correct syntax. If it’s your library and your responsibility then I will try to read it your way. Comprehensive testing and consistency in style are more important than any specific syntax or abstraction.
- smabie 7y agoMy prefered style is a class that's ideally no more than one level deep. Mix in some traits if you need them. Also, as such as possible, make every class/object immutable and the methods side-effect free. I find this leads to a very natural style in which functions are logically grouped together (also, helps with intellisense) but never modify any state. If there are classes/functions that modify state, make it explicit through naming (I append '_!' for Scala). Essentially, what I'm describing, is just functional programming where f(a, x) becomes a.f(x). I find the latter easier to read and lends itself to a fluent, lightweight style. In general, I don't really think the discussion of OOP vs FP or whatever is that meaningful. What is meaningful is mutable vs immutable. Every great developer I've met has agreed with me that mutation should be avoided as much as humanly possible: really the only valid reason to introduce mutation in your program is for performance reasons. And yet, we as an industry still are by and large writing mutation heavy code for no reason at all. Things have gotten a lot better in the last 10 years, but we still have a long way to go. Edit: I'm mostly talking about Scala in this post, btw. I also want to note that I do think deep class hierarchies are a good solution to some problems. The two examples that immediately come to mind are UI APIs and collection libraries. Both these problems are very naturally solved by OO, the essence of which is the deep (as much as necessary) class hierarchies. Very few problems I've encountered besides the above actually require these subtyping. An great alternative approach is row polymorphism, ala OCaml. I've not had any professional experience with OCaml or row polymorphism, so I'm not sure how well it actually works in practice.
- whlr 7y ago> I find the latter easier to read and lends itself to a fluent, lightweight style. Maybe I'm bikeshedding, but I think this is a nontrivial part of why people like OOP languages. Being able to read right to left, without nested parens, makes code comprehension easier. I think the mathematical notation for function application has held programming languages back. I realize that there's a huge benefit to uniformity of syntax across languages, and that syntax is ultimately much less important than semantics, but I wish we could settle on something better than f(x). I really appreciate languages that are willing to do something different (e.g. haskell and lisp).
- peter_d_sherman 7y agoClasses are the design-time version of instanciated Objects, which are active at run-time. Objects, are like little minature software programs, inside of a larger programs. Think fractals, that is, smaller things inside of larger ones... So why have a minature software program inside of a bigger one? The short answer is boundaries. If the larger program is permitted to modify the internal state of an object, then that's really no different than having one big program where all of the variables are global, and any line of code can make any change to any variable at any time. Is that OK? For small programs it is, but the larger the program, and the more programmers that work on it, the more it must be segmented into boundaries, and these boundaries enforced (aka, encapsulation) in order to prevent bugs. That, and the ability to divide code into logical groups which can be individually tested will yield a great deal of clarity and information about a codebase, when done by a competent programmer or programming team. At 100,000+ lines of code (and sometimes a lot less), you will trip over your own code unless you segregate it into seperately manageable sub-systems with clearly defined boundaries. "Divide and conquer" as the old saying goes. On the other hand, programmers inexperienced with Object-Oriented development can make code more obscure, unreadable, and harder to work with... It's a double-edged sword... Use wisely.
- lalaithion 7y agoBut Classes and Objects are not the only way to create boundaries, and may not be the best way.
- peter_d_sherman 7y agoGive an example of when they might not be the best way.
- ball_of_lint 7y ago"Classes are the best way" is a much stronger statement than "Classes may not be the best way". Why do you think classes are the best way?
- naasking 7y ago
- carapace 7y agoMy first programming teacher emphasized that this at-the-time-newfangled "object-oriented" programming was only a way of organizing the code, you still had the same data and functions. Use classes, don't use classes. It doesn't matter.
- kragen 7y agoI feel like dynamically-dispatched open recursion gives you a level of flexibility that you can't get in, say, Pascal — you need first-class function pointers if you don't have virtual functions. BTW, I finally responded to your comment from the other day; sorry about the delay!
- carapace 7y agoWell, Oberon had language-level OOP-- records with methods --which is what my teacher was talking about as just a way to arrange the same data and functions, but the OS also had runtime dynamic message dispatch OOP built on top of but not intrinsically requiring the language-level OOP. I suspect you could implement that in Pascal and make all of Hewitt's Actors model with it. > BTW, I finally responded to your comment from the other day; sorry about the delay! Cheers! I'll go check that out :-)
- kragen 7y agoYeah, the Oberon language added function pointers (I don't remember if those were in Modula-2), and the Oberon OS used that form of language-level OOP to implement its runtime dynamic message dispatch OOP. You can't implement that in Pascal because Pascal doesn't allow you to store function pointers in records (because they might be nested functions, with all that implies). So in Pascal the best you can do is write a switch function for each function pointer type (signature) and use an enumerated type or equivalent to dispatch calls.
- carapace 7y ago> So in Pascal the best you can do is write a switch function for each function pointer type (signature) and use an enumerated type or equivalent to dispatch calls. Yeah, you would have to spell it out and you couldn't do compile-time optimizations, but you could get all the dynamic flexibility you need? Really, what I was saying above is just that if your problem is easier to describe as a bunch of interacting "objects" (like a game or a factory sim) then use OOP, but if it's easier to describe as a set of functions and data structures then don't use OOP. :-)
- remmargorp64 7y agoThe #1 complaint I always see with classes is due to inheritance. When classes inherit from classes that inherit from other classes, you can very easily end up with a tangled web of methods over-riding and extending other methods, and run into all sorts of unexpected behaviors and difficulties wrapping your head around what is going on. This is why (at least in the Ruby world), it's generally considered best practice to avoid using inheritance and just use mixins instead (whenever possible). Completely avoiding classes because you are using them incorrectly is kind of like throwing the baby out with the dirty bathwater. Don't throw away your tools just because you are using them incorrectly!
- arcosdev 7y agoI'm not sure how using mixins is better. Now you don't know half the time what is overriding what. Composition seems to be the better tool IMHO.
- dyeje 7y agoAren't mixins a form of composition?
- arcosdev 7y agoI would argue no they are not. I am thinking of their implementation in python specifically, so my understanding is likely skewed. My notion of composition with classes would be: class Dog(object): def __init__(): self.wagger = Wagger(); #composing the Wagger class in python mixins look like this: class Dog(object, WaggerMixin): ...etc...
- babypuncher 7y agoIf I had a nickel for every blog post I've seen highlighting an incorrect usage of a pattern as a reason not to use the pattern at all...
- Vinnl 7y agoAren't classes in Javascript mostly syntactic sugar to make inheritance easier?
- poppabox 7y agoI don't use classes I just use structs and functions and modules like classes are supposed to be used when you do it right.
- lmilcin 7y agoThere are people who think every carpentering problem can be solved with chisel and the only book they ever red is one that says how to solve every problem with a chisel. The response to this is not to throw out all chisels. The response is to learn to use various tools and once you know how to do this, how to apply different tools to different types of problems. There is no shame in overusing a tool for a period of time. The novice carpenter must be able to play to learn limitations of tools but also to learn which inventive ways to use tools are actually helpful. Master carpenter will understand that playtime is necessary step in getting to mastery and will not call out novices for their overuse of the chisel but instead will encourage play with the intent to speed up education.
- deleted 7y ago[deleted]
- fuzzy2 7y agoHm. Not sold. Sure, you can write terrible code with classes. But that also works great without classes. > I’m terrible at making good abstractions with classes. Then don’t. Or only do it after everything is done and you have explored the domain. Don’t try to be clever from the start. Unless you have understood everything, it will backfire. > But as other classes extend and override those methods to address their version of the problem space, they usually end up having some other responsibilities in addition to the original task. Inheritance gone wrong. If a derived type behaves differently, it should not be a derived type. See Liskov substitution principle. Just because a hash map is somewhat like a list of key/value pairs doesn’t mean it should derive from one. It could also be a problem of missing separation of concerns/responsibilities, which is entirely unrelated to OOP. > Secondly, I’ve noticed that classes have a tendency to grow large. Yes, I did see many big classes. They were always the result of... missing separation of concerns/responsibilities. The fabled god objects. The section about state management appears to be very much React-specific, too. So, not about classes.
- cryptica 7y agoLarge classes are not always a bad thing. It's better to have a large class with 1K lines which you can break up at a later date than having lots of small classes which have unclear or overlapping responsibilities and which will be difficult to reorganize later. It's much easier to split up and categorize parts of a large blob of code than to re-categorize and merge bits and pieces of logic that are scattered all around.
- mark-r 7y agoWhat kind of coding do you do where a 1K class is considered large?
- cutemonster 7y agoWhat do you consider large? I think classes without internal state, can be large and still simple to understand. But if many mutable fields and internal constraints, then things can get complicated. Better keep such classes smaller
- catacombs 7y ago> I Don't Use Classes OK, cool.
- Xcelerate 7y agoEver since I discovered Julia a few years ago, I’ve found I can’t stand using classes any more (particularly in Python). Multiple dispatch just feels so much more elegant than trying to dispatch everything on the type of an implicit first argument.
- snambi 7y agoSounds like an idiot with opinions.
- crimsonalucard 7y agoI came across a random answer on quora as to why classes are bad (as opposed to structs). It gets into the very nature of program design and organization. https://www.quora.com/Is-senior-full-stack-engineer-Ilya-Suzdalnitski-right-that-theres-no-objective-evidence-that-object-oriented-programming-OOP-is-better-than-plain-procedural-programming-And-is-OOP-in-fact-a-trillion-dollar-disaster/answer/Bernard-Yip-2?__filter__=all&__nsrc__=1&__sncid__=4384812242&__snid3__=7127597147 https://www.quora.com/Is-senior-full-stack-engineer-Ilya-Suz... Be sure to read the comments, it's pretty interesting.
- gentleman11 7y agoThe only classes that really make sense are ones that model something in the real world to some extent. These classes are incompatible with the single responsibility principle however and are considered/lead to bad designs with a lot of coupling. Most of my classes get instantiated very few times as well, so the blueprint concept is only sometimes applicable. Inheritance, one of the three pillars, is a trap. The whole OOP framework is a bit muddled and to make it work you end up in this web of contradictions. You suspect there is a better way but until you find it, you use Java or C# because that’s what your company/tools use
- threatofrain 7y ago...so when do classes make sense then?
- gentleman11 7y agoPolyymorphic implementations are nice - Eg, the State pattern, or using interfaces to abstract out a server during development. Command pattern. Things you have a lot of. It’s not the entire app though, just subsets of it
- dpcan 7y agoBest thing about 20 years of freelancing has been that I have NEVER been asked about, ridiculed for, or heard complaining about my code. Nobody even sees it, or cares. It's efficient and responsible code in my opinion, but I don't have to care about this strange problem where programmers actually have opinions about how code is put together. I don't know how you guys put up with it, I'd go nuts if someone told me I had to use more classes, or a certain language, or tabs over spaces, or whatever.
- SahAssar 7y agoI think we put up with it because personal development requires outside feedback. I couldn't imagine getting better at the pace I am without both having to justify and explain my decisions to others. I don't understand your viewpoint at all. The best part about coding for me is getting a review with a lot of interesting comments about how it could be done differently. It jogs my brain down paths I would never have seen by myself.
- kareemm 7y agoAs a consultant (not the OP) one of the best parts of writing code is having it solve real business problems (and getting paid well for it). It's a point of personal pride to write reasonably good code, but the structure doesn't matter to the person paying the bills - the outcome does. It's like living in a house: as long as it doesn't fall down and doesn't cost unreasonable amounts to maintain, I don't really care about how well-formed the mortar is, or that the drywall is hung perfectly according to company standards. I just want to live in the house and not worry about it.
- SahAssar 7y agoWell, what I'd say to that is: * Somebody maintains that code. If it is only you you have a bus-factor of one if none of it is actually reviewed. If it is not you you are imposing a single persons idea of what the business problem is and how it should be solved on the maintainer, which they might not agree with * Personal development depends on realizing when you are wrong or have used the wrong tool. If nobody challenges your decisions you will probably not realize that as often (or at all) and therefore not improve as much. * Solving business problems means solving problems for a business. Usually businesses evolve over time, so the business problems might change. Who will understand the code in that case? How do you know the code is understandable to other people if nobody else reviewed it? And as for the house example: before buying that house I would definitely hire somebody else to check it, and that is legally required in some countries. I don't trust anyone to rate their own work. In this case the person who hired your consultancy bought the house, and they should definitely have an expert that did not build it check it. --- Just a FYI: I'm also a consultant, but work on a project where we have discussions about how to structure the code and do code reviews and so on.
- keymone 7y agoGood advice. Don't use classes. At least resist using them as much as possible. Consider this simple aspect: one of the key differences between a class method and a separate funcion is implicit (and sometimes explicit, though that's not much better) self/this parameter that allows access to all object fields. Essentially you're opting in to grow the input of every method call by all the data held by an object. That's a mistake on many levels: - it is one of the most important "best practice" advice to only pass the data that function needs to do the job, with classes you're breaking that advice immediately - ease of access to additional data doesn't leave room to think if it's actually good idea to do so, factor in laziness and inexperienced contributors and you get methods that grow in complexity much more than they should - testing anything becomes torture, as unlike with simple function class methods now depend not only on incoming arguments but also on state of the object itself, i've seen tests with dozens of lines of setup for single line of test Prefer simple pure functions, prefer builtin data types, prefer immutability, only pass arguments that function needs to do the job.
- Udik 7y ago> only pass arguments that function needs to do the job. Why would users of that function bother to know what data it needs to do its job? It seems you'll end up polluting your whole code with the implementation details of one of its components. What happens of you want to change those implementation details?
- keymone 7y agoBecause the two alternatives we’re talking about here are ‘foo(bar, baz)’ where foo, bar and baz convey meaning and ‘object.doWork()’ which means nothing and explains nothing. Yes, it’s a spectrum and there’s a lot of space in between those alternatives, I simply believe being closer to the first option is better than to the second. Pure functions are conductive to transparency, objects, methods and class hierarchies are conductive to obscurity.
- babypuncher 7y agoI once tried to drive a nail with a socket wrench and it went poorly. I have since cleared my garage of all socket wrenches because they are clearly useless.
- keithasaurus 7y agoMy pet theory about the abundance of classes is that they only gained popularity because of their inherent complexity. There's no one, authoritative way to use them, so a bunch of people can went to conferences and wrote papers about SOLID or encapsulation, or whatever. So people thought there must be value in them. It takes a long time for a lot of developers to realize anything you can do with classes you can do without. And if you structure your code without classes, it tends to be more refactorable because moving chunks of code in and out of different contexts is a breeze. The only things I find potentially useful about classes are: - portability (but you can get that with modules, caveat that not every language does modules well) - auto-complete in a noun->verb context
- bob1029 7y agoI've been all over the board on this throughout my career. Currently, we are looking at a substantial reduction in the number of types in our codebase. This would imply fewer & larger classes. Which is totally fine, as long as you know how to manage that complexity. Our approach for managing this complexity is to simply say that each class in the domain model aligns to the bounded context of a particular business unit, process, or activity. So, instead of modeling a bunch of different types within some namespace, the type itself represents the entire extent of that business concern, including any business facts as instance properties. Complex types are modeled as nested classes within each context. Nothing is shared between contexts except for trivial domain-wide concerns in the outer scope. This allows for very easy serialization of business state in and out of a database (among many other things). Consider the power that a method override might have in this realm if you wanted to construct a hierarchy of such contexts. An example of one of these concrete types might be SelfCheckoutContext (if you were working in a retail business domain). This type could be derived from a wider CheckoutContext which could be simultaneously shared other concrete contexts like HumanCheckoutContext. In our approach, there is also a lot of stuff that exists outside of each context (Views, Services, Repositories, etc). We basically treat these items as a platform layer for the contexts to operate on top of.
- CaptArmchair 7y agoI hear some echoes of domain driven design / development in your comment. Have you looked into that?
- bob1029 7y agoYes absolutely. We took what we felt was the bare essential idea of DDD (bounded contexts) and ran with it. The fewer patterns the better. Allows you to work with fewer constraints, assuming you are responsible with the added power. Bounded contexts are IMO the most powerful abstraction out there for accurately constructing a very complex business system. They directly get at the root of the problem, which is aligning the implementation with the same grain as the business. They also permit complete isolation of various business units which might have unique perspectives into what would otherwise be shared (and increasingly-convoluted) models. In our latest implementations, models are never shared between contexts. There might be a few models defined in the outer domain scope for things we believe apply universally to the application. Any cross-context communication is achieved via simple mappers.
- ben509 7y agoThe fugly state in objects is tricky. One way to look at a class is to treat it as a kind of coroutine. Consider some pseudocode: class Stats sum Num = 0 sum_squared Num = 0 count Num = 0 acceptNum(x) sum += x sum_squared += x ^ 2 count += 1 mean() return sum / count variance() # I think this is right... return (sum_squared / count) - (sum / count) ^ 2 But that could just as well be: coro Stats sum = sum_squared = count = 0 result = nothing loop msg = yield result switch msg.name case acceptNum sum += msg.x ... case mean result = sum / count ... We don't do objects like this. We really want a file object, for instance, to be something like: coro FileReader # Not opened yet raw_handle = popen(...) # Reading ... if msg.name == read yield read(raw_handle) ... # Data exhausted ... if msg.name == read yield eof ... # Handle closure ... if msg.name == read throw ReadOnClosedFile ... (I think you could handle inheritance with a scheme like this, presumably a "subclass" would simply yield to the "superclass".) What's nice about that is the state is explicit in the structure of the coroutine. Ideally, we'd have some sort of protocol types that reflect that we're accepting different kinds of messages at different stages in an object lifecycle. In this example, after a file closes, it's a type error to try and read from it. Instead, we generally munge it with properties, depending on lots of checks instead of structure. We can solve state through more classes, of course. The Builder pattern handles a specific case of this quite nicely, you have one mutable construct that then returns an immutable construct. But to use that more universally would lead to a profusion of classes.
- codr7 7y agoNeither do I if I can avoid it, I use type hierarchies and generic methods. Unfortunately it's not very popular; the only supporting languages I know of are Common Lisp, Julia & Perl 6. I've designed and implemented several scripting languages [0] over the years, but they all have this pattern in common, I've never felt tempted to implement classes. [0] https://github.com/codr7/gfoo https://github.com/codr7/gfoo
- jbjohns 7y agoDylan also supports it and it can be done in Python with a library [1]. I don't know if you'd count it but it's also straight forward in Haskell [2] at the type class level. [1] https://en.wikipedia.org/wiki/Multiple_dispatch#Python https://en.wikipedia.org/wiki/Multiple_dispatch#Python [2] https://stackoverflow.com/questions/26303353/can-multiple-dispatch-be-achieved-in-haskell-with-pattern-matching-on-type-class https://stackoverflow.com/questions/26303353/can-multiple-di...
- lizmat 7y agoPlease note that Perl 6 has been renamed to Raku (https://raku.org https://raku.org using the #rakulang tag on social media). If you want to stay up-to-date with events in the Raku world, check out the Rakudo Weekly News: https://rakudoweekly.blog https://rakudoweekly.blog
- gumby 7y agoI really like programming in C++ and don't write a lot of classes. They are a useful tool in the toolbox but hardly the only one. Well, looking again, I do use a lot of structs (or their moral equivalent, using 'class' to define a struct with some protected state) but don't use inheritance much.
- sreque 7y agoClasses are a means to an end: objects. Objects give us the three core components of OOP: * encapsulation * message passing * late binding Things like inheritance tend to muddy the waters. If you aren't benefiting from the above 3 things, then you are probably either using objects wrong or you are applying them to a problem for which encapsulated, late-bound message passing isn't a good solution. As a corollary, state management and OOP are in my opinion orthogonal. You can write and program with immutable objects just as you can with mutable ones. I do agree with some other comments that OOP in college isn't taught very well. Most people leave their first OOP class not really knowing anything about OOP or why it's useful. My hunch is that OOP is something that should be taught later in a program, not earlier.
- mirekrusin 7y agoSparkles of inheritance can be good. I use them sometimes to aid functional combinators.
- sreque 7y agoRight. I'm not saying inheritance should never be used; just that it can be a distraction for learning what OOP is really about. If I were to teach OOP in Java to new students, I might start out just focusing on interfaces.
- threatofrain 7y agoIMO, when people talk about OOP they aren't talking about bags of static functions -- that's just a namespace. The interesting objects are those which hold state.
- sreque 7y agoYou can have an encapsulated object that is immutable and is not a bag of static functions anymore than any object is a bag of static functions. Look at Scala's interfaces for immutable collections. * they are encapsulated. You don't know how the data is stored internally. * There is message passing involved (at the JVM level). The methods are virtual and you don't know exactly which method is being called. There can also be multiple implementations using different data structures. * Late binding is again as defined by the JVM (interface table method lookup) In contrast, a static function is no different than a global variable. When you call it, you know exactly (sans JVM linking) which function you are calling.
- cryptonector 7y agoThis is like so many "OOP is bad" posts. Yes, OOP is bad. OOP is bad because inheritance is not as good as interfaces. OOP is bad because typically it puts state changes inside mutable, shared objects, which makes concurrency difficult. Java has interfaces and generics -- use those instead of inheritance. C++ has templates -- use those instead of inheritance. Design and implement interfaces that operate on immutable, preferably linear data types, and concurrency will be easier.
- kalekold 7y agohttp://nomad.uk.net/articles/developers-who-hate-on-oop-don't-know-how-to-use-it.html http://nomad.uk.net/articles/developers-who-hate-on-oop-don'...
- TurboHaskal 7y agoIt's 2020. You're preaching to the choir on this. I myself use classes just fine. I love CLOS and I'm having a lot of fun with OOP on Forth as of late.
- cryptica 7y agoI think the problem is that most developers did not learn OOP correctly. I noticed a lot of developers these days don't understand basic concepts like encapsulation and class dependencies. When I was studying OOP at university, there was an entire course on UML diagrams and that taught people how to structure OOP software. Good OOP design tends to produce elegant-looking UML diagrams with relatively few interconnections between components. I don't always physically draw UML diagrams, but when I visualize a project in my head, it looks very much like a UML class diagram. I can't imagine how it's possible to reason about complex software in any other way TBH. Sadly, if I had to draw a UML class diagram for most real projects which exist today, the relationship lines between UML classes would be crossing all over the place and it would literally look like a tangled mess (definitely more like a graph than a tree). The people who designed the software had no vision for it, they just kept adding stuff without ever actually understanding how it all fits together or thinking about how to minimize complexity and make it more elegant by minimizing the number of components and the interconnections between them. I think that, in a way, functional programming is more forgiving when it comes to allowing people to keep adding stuff on top without having to visualize the system as a whole. It doesn't mean that FP adds any net value over OOP. I would argue that being able to clearly visualize the whole product and the interaction of data and logic is essential regardless of what paradigm you follow.
- cmrdporcupine 7y agoThe first object oriented programming language I learned (LambdaMOO) didn't have classes. It's entirely conceivable to do nice clean object oriented code using a system which has only prototypes. In fact it encourages one to think about the important parts: encapsulation and delegation without becoming obsessed with classification.
- lliamander 7y agoThe biggest upside of programming with objects/classes is dependency injection. And no, I don't mean DI frameworks like Spring. I mean simply the dynamic (i.e. runtime) composition of program behavior, typically by passing collaborating objects into the constructor. (Functional programming languages have something similar with higher-order functions.) Contrary to prior claims, objects aren't great at representing real world entities: to do that you would also need to add a notion of time and concurrency to those objects (e.g. actors, processes, services). However, they are a great way to represent the components and capabilities of such entities.
- austincheney 7y agoI have always associated classes with inheritance. Perhaps there are other scenarios where classes are unrelated to inheritance. I don't know as I intentionally avoid classes all together. I avoid inheritance because inheritance increases complexity. By complexity I simply mean it in terms of complicate, or to make many. I am not speaking to anything related to challenges, easiness, or hardships. Inheritance is a practice of a concept called polyinstantiation, which allows easy dynamic replication of an object in memory so that many child or cloned objects are independently extensible. * https://en.wikipedia.org/wiki/Polyinstantiation https://en.wikipedia.org/wiki/Polyinstantiation * https://en.wikipedia.org/wiki/Complexity https://en.wikipedia.org/wiki/Complexity
- croo 7y agoIt's better to associate classes with encapsulation. I generally don't do inheritance apart from a few weird cases because composition over inheritance works way better.
- westoncb 7y ago> I have always associated classes with inheritance. This is a common pattern I've noticed in discussions about OOP. There is a tendency to reduce it to "inheritance," which turns the conversation into a straw man: "here is why inheritance is bad, therefore OOP is bad". Same goes for identifying classes with inheritance. Their primary feature is grouping of methods and fields into (potentially) encapsulated units, and being a mechanism for abstraction: classes are more general than their instances and serve as a general pattern; upon construction they are 'configured' through parameters to their constructors and particular forms are arrived at.
- room271 7y agoMost useful programs have state. As the program scales you need to break it up into parts (or 'modules') with clear interfaces. Usually these parts own state. The problem with OOP is that it assumes you need lots of these parts. Whereas really, you might have a few or possibly a handful. Use, class-like constructs to represent these, and use normal functions within these, with ideally some kind of polymorphism that also doesn't require classes. For small programs, don't even bother with this. Just use pure functions for key logic, and unit test those, and delay testing anything with state for as long as possible. When you do need to, leverage interfaces and stubs/fakes (not mocks) to test them. That's what I do now anyway.
- ch_123 7y ago> Secondly, I’ve noticed that classes have a tendency to grow large. They will collect pieces of functionality that need to live in the context of the class, usually to access the internal state of that class. This dependency on the internal state makes it difficult to break the methods up into logical chunks. Sounds like they need to pay more attention to the Open-Closed principle :) It's not really clear to me how a random grab-bag of functions is better than a random grab-bag of methods. If the functions in the too-large module can be broken up into small units without damaging cohesion, why can't the same be done for the class with two many methods?
- sunstone 7y agoInterfaces are good though.
- winrid 7y agoI think I understand what the author means. When writing Java code I prefer to use public members in my data classes and only use getters/setters sparingly. Also, I prefer to have the logic in until classes if I can. I suppose I'm not a huge OOP fan. Of course I have to follow whatever patterns my teammates follow and hardly anyone writes Java like that... But my personal projects I think have much cleaner and easier to understand code with this approach.
- mr_tristan 7y agoI've noticed that in large Java hierarchies, where I spend a lot of my time, people often use interfaces where they really should be using modules. Like, there's this vague concept of a "service" and methods of this service are bound together via an interface. Which ends up coupling all kinds of weird dependencies in the class definition. My guess is that this is a similar case in Typescript that the author is describing. So, this approach is, instead of having IFooService with two methods, IFooService.doOneThing and IFooService.doAnotherThing you just have a package "fooservice" with functional interfaces DoOneThing and DoAnotherThing. This seems to still play nice with most of the tooling, and often, keeps the code simpler and makes it easier to refactor. DoOneThingImpl and DoAnotherThingImpl can easily share code, but that aspect shouldn't matter at all to the caller. Another way of thinking about it: don't worry too much about a perfect interface for layering your business logic initially. Start with just some functions, then as you spot patterns you may recognize there's a state pattern that does things better. I don't think this is really specific to Typescript.
- michelpp 7y agoI also tend to avoid writing many classes (in Python). Jack Diederich has a great video called Stop Writing Classes: https://www.youtube.com/watch?v=o9pEzgHorH0 https://www.youtube.com/watch?v=o9pEzgHorH0 It's about Python, but the observation can apply to several languages. Pithy quote: "I hate code and I want as little of it as possible in our product."
- randomdata 7y agoJavaScript, and thus Typescript by extension, lived for many years without classes. The addition of classes is relatively recent. It stands to reason that classes are not heavily adopted since since a lot of the foundational work happened when classes were not even an option. In contrast, I cannot image being able to get far in Java without using classes. The concept of using classes is central to that language.
- chrisseaton 7y ago> I cannot image being able to get far in Java without using classes Well you literally can't write a Java program without writing a class.
- lincpa 7y agoYes, I never use classes, only data and pipes. [The Pure Function Pipeline Data Flow v3.0 with Warehouse / Workshop Model](https://github.com/linpengcheng/PurefunctionPipelineDataflow https://github.com/linpengcheng/PurefunctionPipelineDataflow) 1. Perfectly defeat other messy and complex software engineering methodologies in a simple and unified way. 2. Realize the unification of software and hardware on the logical model. 3. Achieve a leap in software production theory from the era of manual workshops to the era of standardized production in large industries. 4. It's the basics and the only way to `Software Design/Develop Automation (SDA)`, SDA is an innovative and revolutionary approach to develop large-scale software, just like `Electronic Design Automation (EDA)`, because it systematically simulates an integrated circuit system.
- jacobn 7y agoThey’re using TypeScript. Ts classes are horrible: you have to explicitly use “this.” to access anything in the class - it’s as good as it was possible to make it with the JS legacy, but it just doesn’t work very well. Using namespaces with state passed to the relevant function ends up being basically just as easy, and sometimes easier as you end up using more closures and can access enclosed variables directly. Classes in languages designed for OO work a lot better.
- bfung 7y agoOne of my favorites: https://wiki.c2.com/?ClosuresAndObjectsAreEquivalent https://wiki.c2.com/?ClosuresAndObjectsAreEquivalent The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures." Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress. On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man's closures." Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." At that moment, Anton became enlightened.
- beefbroccoli 7y agoI use classes a lot in typescript, but not for OOP. Just glorified objects with a constructor and getters.
- fetbaffe 7y agoTypescript works well without classes because you have great type system, you can implement an interface on the fly by just passing an object with the correct properties to fulfill it. But the class based architecture doesn't necessary have to be as bad as described in the article. Inheritance is somewhat of the old school of OOP, modern OOP rarely use inheritance, it uses composition, just like a functional code base. In my projects my classes are always marked as final. Can't even remember the last time I inherited a another class, maybe if I interacted with some legacy code. It is important to separate your classes, what classes can have state & what classes can not have state. Classes with state should be small & classes without can be large, because stateless classes can easily be reused. Classes have the main advantage that you easily inject dependencies in the constructor (composition), e.g. a repository depending on the database driver. You can do that with functions as well, e.g currying, but I find it a bit more messy if your language is not specialized against that use case & with a class you "curry" all class methods at once. If you don't inject your depencies in your functions you will have a much harder time to reuse that function & test it. I agree with that class based GUI code rarely works, this is because GUI (if it is HTML or OpenGL) does not translate well to a class with hierarchies. GUI code have tendency to more be procedural.
- rkagerer 7y agoFor a small to medium sized codebase, functions and structs are simple, clear and don't obscure state.