 I recently posted a video called object-oriented programming is bad, in which I made the theoretical case against object-oriented programming and why you should really never do it. And a number of people complained about the title, suggesting that it was somehow a clickbaity, but I disagree. I chose the title because I think it's really true. I have nothing nice to say about object-oriented programming. I don't like it. I think it's really been a disaster for the industry. And so to follow up that argument and see it through, I wanted to present some concrete examples, and this one's upping the ante, so it's called object-oriented programming is embarrassing. In this video, I'll have four examples, but they'll be really short, less than 200 lines in each case. Two of them are in Java, and two of them are in Ruby. And I'm going to show how if you rewrite them into just a simple procedural style, the resulting code is much better, and I'll try and discuss exactly why. And just to make the comparison fair, for each example, I won't change the language. So from Java, I'll rewrite it in Java, and if it's in Ruby, I'll rewrite it in Ruby. Be clear. I did not cherry-pick these examples at all. I just basically took the first examples I found on YouTube or Vimeo of some speaker giving a talk explaining how object-oriented design is supposed to work and how it's going to make your code better. These are literally the very first code examples I came across when I started looking for them. So if you think the examples are unfair, do send in suggestions. So I would want to follow up with even longer examples, because I know people are not going to be convinced with just 200 line examples. You want to say, oh, well object-oriented programming virtues don't really come out until you're talking about much larger samples of code, because it protects your code from becoming spaghetti when it gets larger and larger. Ideally we would actually put that theory to the test and take a 500,000 line program and rewrite it from object-oriented code into procedural and vice versa and see what we get. For obvious reasons though, no one ever does that. But what I would like to do at least is next take an example of, say, a thousand or two thousand lines, and then next maybe some example that's, say, 10,000 lines. And ideally, I don't know, rewriting like 50,000 lines is a lot of work and I don't know if there's an ideal candidate for it, but maybe one could be found. That would be a long-term project probably. In the meantime, if you have some suggestions of some code example you've found, some get a repo of a complete program or maybe even just some submodule of a program that's like 1,000 to 2,000 lines or 10,000 lines, I'd very appreciate if you sent me some suggestions. Preferably it would be something in Java or JavaScript or Python or Ruby even though I prefer Python. It's not a strict requirement, but it's better if it's in language that everyone knows because I want to have it be accessible to as many people as possible. So probably preferably JavaScript though if it makes sure it's an example that is actually in an object-oriented style. Not perfunctory object-oriented style, but actual object-oriented style. So before getting into the examples, very briefly, I just want to recap very, very broadly my argument against object-oriented programming. This is a new formulation I suppose than what I originally stated in the video, but it's another simple way of thinking about it. And that is just let data be data. What object-oriented programming demands is that we're supposed to be conceptualizing our data as not just data, but as some sort of thing with responsibilities and something which acts in the world. We basically have to imbue our data with intent and agency. And that's just a fundamental mistake. Data does not do things, data is just inert, and that's the way it should be. And you're just adding confusion if you try and conceptualize your data as some kind of actor. The other side of the coin is that we should let our actions just be actions. We shouldn't have to conceptualize all the things we want to do in code in terms of some kind of data. We shouldn't have to nounify all our verbs. It is this conflation of action with data and vice versa that I think makes object-oriented programming so mystifying. If you go down that path, if you follow the advice of the object-oriented programming gurus, I think what happens inevitably is that writing code then begins to feel like you're having to solve the problems of analytical philosophy and platonic essentialism. You have to constantly question the nature of the entities you are creating in your code. If you create a chair class, for example, you have to consider the true nature of a chair, you have to determine its essential aspects. Because if you get that wrong according to these gurus, if you draw the boundaries around this entity incorrectly, you're violating some principle or another of object-oriented code, it's going to bite you in the ass in one way or another, and your code's going to become spaghetti, and all the guarantees they promised about what object-oriented programming is going to deliver for you. All their guarantees are void and you can't hold them responsible because you didn't follow one supposed principle exactly as they intended. Notice then the justification for object-oriented programming is circular. You try and do object-oriented designs and they don't seem to really quite work and you don't understand why things are supposed to be arranged in this way and what benefits you're supposed to get. Well, it's because you're just not doing it right, so here are 10 more principles and guidelines. In other words, so the first example is from a talk given by Sandy Metz called Solid Object-Oriented Design, you can find it on YouTube. The centerpiece of the talk is a really short class that processes a patent. It downloads a patent file, it parses the file, and then it updates that patent information in a database. And the core thrust of her talk is she's attempting to demonstrate the so-called single responsibility principle, this idea that your classes and generally also your methods should only be doing one thing. What exactly does it mean for something to be doing just one thing and one thing only? Well, that's a disputed point. The definition given by Robert Martin, aka Uncle Bob, who coined the solid principles in SRP, the single responsibility principle, his definition is that a single class, a single unit, should have only one reason for change. Now, what exactly is one reason for change? Well, it's something he explains at length. I think it's even after explanation, I think it's a very nebulous idea, which is part of the problem. But putting that aside for the moment, let's look at her example class. As you can see, it's written in Ruby, and it has four methods, starting with run, which is really where all the action is. That invokes the other three methods, download file, parse, and then update patents. Sandy, however, is not satisfied with this design and says that you shouldn't be either, because what's going on here is we just have too many responsibilities in one class. I actually broadly agree on that point. It's very strange that this one unit of code is both downloading files, but then also parsing and updating the database. It's mixing a lot of disparate stuff and it's one unit of code that, yes, do all concern patents, I suppose. But there is something really weird going on here as evidenced by the simple fact that classes are supposed to be data types, and yet, look, there are no fields in this class. So what the hell is this thing, really? The real answer is that it's just this tiny namespace, and that's all that's going on here. That's all this class really is, except it's worse than that because in object-oriented code, to use a class, you have to instantiate it. So we have to manage this instance thing in a totally nonsensical way. We're managing this instance that has no persistent value whatsoever, and so you immediately dispose of it after invoking the run method. What is this thing? Why does it have to be a thing? It serves no purpose being a thing. Sandy's prescription is given this nonsensical class, the solution is, as you might expect, more classes. In her rewrite, she ends up with first this config class, which separates out and effectively generalizes the configuration concerns of this program, which is a sound idea, I think, but we have this constructor, which the initialize method, which takes in some set of options potentially. I don't know exactly what purpose they serve, but anyway, so the options tell us where do we get the file name, I believe, for this YAML file that has configuration stuff that's loaded, and from the YAML file, we get this data, which is then, there's some weird meta business going on here in Ruby, in her define methods for environment method, the names are turned into effectively properties of the config class itself, or rather, actually methods of the config class itself that return values corresponding to the key value pairs of the configuration file. Effectively, in the end, we get a config instance that has a method for all the configuration property names, and you just invoke the method to get its associated value. So then, my objection to this class is, why do we need it at all? Why can't we just have a hash map? Instead of having a constructor, we have some function taking in the options, doing all the same business, parsing this YAML file, but why not just return a hash map? Why do we need this class? She also moves all of the downloading business into its own class called FTP downloader, and a similar absurdity leaps out here. Why is this a class? We're initializing this config property, which we're never gonna use again. Any user of the FTP downloader is never gonna access this config property. It's just they're gonna call this download file method. It's you're creating a whole instance of a class just to call this one method. Why do we have a class? Why is this not just a function? Lastly, here's her rewrite of the patent job class. The difference is there's now no downloader method. Instead, in the constructor, we create a config object and then pass that to create a downloader object, and this downloader object is preserved in a downloader field, which is then later used when we call the run method. And I'll note here, I'm surprised she doesn't use constructor injection. It's odd that she has her patent job actually creating these config objects and downloader objects, as much as I don't like object-oriented programming. If you are going to do it, I think for a very good reason, you do use dependency injection because that, well, that's a whole tangent, but basically it solves the problem of having to wire together all your objects, or it's a much more sane way of doing it. So I'm surprised she doesn't do that here. It seems wrong that she's instantiating these objects inside this other constructor. Anyway, that's a moot point because the code should just be this. It should just be straight, procedural code is a bunch of kernel methods as they're called in Ruby, basically just functions. We don't have to ponder the significance of any classes and speculate as to what their fields might signify, why they exist and what purpose they serve. And so say instead of a config class, we just have this get config function which returns a hash with all the fields parsed out of the YAML file and a story. We don't have to think about any constructors and we don't have to do silly things like creating objects immediately calling one method and then never using the object again. We don't have to do silly things like that. In fact, I would go a little further here. I would take these little tiny functions that don't get called anywhere else except in this one place in the process patent function and just put them in line there. Maybe speculatively in the future as the program evolves, those might become larger, more involved operations where maybe at some point it'll make sense to split them off into their own functions because they get really involved. But as it stands, it seems really silly. You know, if I can come across your code base and encounter just three functions I have to understand instead of five. Well, of course at this tiny scale it's no big deal either way. So it doesn't really matter. But if you can show me a code base where instead of a hundred functions I have three fifths of that, that's just way more tractable. It's just a lot easier to get a foothold and start understanding the code because they don't have to contemplate the potential uses of these functions and where they might get called. The next example comes from a video by Derek Banas from a few years ago. It's this whole series he has on objectoring to design. There are 11 or so videos but this code comes from the second one of those. To be fair to Derek, his other videos on programming, the more recent ones I've seen are actually quite good. But this series is terrible because just the nature of what he's trying to teach. It's just disastrous. So in this example, he is writing a coin flipping game. Yeah, you know, one person throws a coin into the air and then someone else calls it heads or tails and there's a winner. It's not gonna actually really be an interactive program. It's just we're simulating the result to get the end result. So one of two named players wins and randomly it's either heads or tails. And that's it. And we just print out so-and-so one with a coin flip of heads or so-and-so one with a coin flip of tails and that's all we're going for here. In this video, he's trying to demonstrate how to do formal modeling using UML techniques. UML was a Stanford Unified Modeling Language, Universal Model, something like that. Who cares? It's fucking garbage. As you can see by this, this is his use case. He has his class diagram. He has his three classes. There's a player and a coin and a coin game. And you know, the class diagram is one thing but then there's the actual composition of instances. So you have an object model, which is different. And then we have a sequence diagram, which is basically just sort of like a visual representation of progression of time and what talks to what. And then from all of that, he writes his actual code. So we have four classes here. The fourth class is just a coin flipping game in the bottom right, which is just a container for main because he knows Java and everything has to be a class. I'm not even going to walk through this code. It's totally confused even for something so simple because just here, this is all he needs. He doesn't really need classes of any kind except for container for main. And then in this main, okay, we'll split out, play game to another method, I suppose. Doesn't really matter in this case. We just take in two strings, we randomly pick one of them and then we randomly pick heads or tails and print out so and so one with a flip of whatever. And that's all we need to do. And then we have this in a loop because that's what he did. The user is prompted, do you want to play again and you hit something and it just goes again. That's all. I don't think I have to actually make an argument about why this code is so absurd. Just look at it and then look at what you actually just needed to do. At a minimum, the lesson here is that UML is just fucking garbage. Don't ever use it, it's crazy. If you want to plan out your code ahead of time, do so to a degree, of course. Just plan out your data structures, write a little bit of pseudocode. Don't try and diagram anything that never works. It's just a pain in the ass to deal with. All the tools for modeling, the boxes and the arrows and everything, they're all terrible. And even if they were good, what's stupid about visual representation of code is that past a very tiny amount of complexity, you have all these boxes and then you need to draw a bunch of lines between them. And very quickly, you have so many lines that it's really hard to visually parse. And why didn't you just write code to begin with? Because code has the nice virtue of you have a bunch of functions and they can all just refer to each other by name. It's past a certain point of complexity of things all tied together. It's better represented as just text and named references. That's the main reason why visual languages have never really gone anywhere. It's just a fundamental problem. The next example is another one from Sandy Metz called All the Little Things. I don't mean to pick on Sandy, it's just I found one of her talks and so it was easy to find another. In this talk, she presents an example of some ugly code, some ugly branching logic. And she demonstrates how this ugly branching can be more cleanly represented with polymorphism. So looking at this actual code, this was not a real code example clearly. I think it was an arbitrary example and these are like random World of Warcraft references apparently. And looking at this, I think we can all agree that this is ugly code. You don't wanna see code like this. If you're gonna have a lot of complex logic, it should be complex for a reason and you can very quickly see in a few places that this doesn't need to be so complicated. This could be cleaned up definitely. But here's her solution. She puts everything in this class, gilded rows, I don't know, I don't understand what that's supposed to signify but whatever. And inside we have this class item with a tick method and sort of an abstract class item with an empty tick method. And then we create three specializations of item. We have normal, brie and backstage, each with their own tick methods because what she determined with the original branching code is that we really have four mutually exclusive cases. We have the case where we do nothing. We had one case where the string was equal to normal, in other words equal to aged brie and another where it's equal to backstage passes to a Tefal 80 et cetera concert. Again, some kind of World of Warcraft reference there. So at the bottom here, she has default classes item but then there's the specific cases. She has this hash map, normal maps, the normal class, aged brie to the brie class and backstage yada yada to the backstage class. And then at the bottom we have the self.for method. I think the significance of self here is that this makes it basically a static method of gilded rows, it's a class method rather than the actual instance method. So you write gilded rows.for and you pass in the name which is gonna be a string like normal and then the quality and days remaining and you get back an item object upon which you call its tick method and you get the same branching behavior we saw in the original code. So all of this is clever, I suppose. It's certainly making full use of Ruby language features but if you don't care about doing that why didn't you just write this code? The problem with the original code, the reason it was so ugly is because it didn't correctly first identify the four major mutually exclusive cases. And if it had, there's no reason it can't be expressed as a switch or in a felt slider. The way she presents it in the talk is that polymorphism swoops in and saves the day and makes the code much cleaner but there's no need for it. You can just write a damn switch. If you can figure out that the major cases are the string being normal or aged brie or backstage yada yada, then you can just as easily write this code. You don't have to involve polymorphism. Why would you want to introduce more concepts of data types into your code when you don't need them? That's just perverse. The procedural code version is just as clean and actually much cleaner because it doesn't involve extraneous concepts. In fact, I would actually make this a little bit more procedural. Here we're still using instance variables as if this is the method of some class but in a straight procedural version we don't really have classes so we'd pass into tick, we'd pass in a name and then some object and then operate upon its members. Now I've seen this polymorphism in a place called branching idea in other places and the larger argument generally given is okay, so in your code you tend to have a switch over one piece of data and you're gonna have parallel switches in many other places in code. And so it's better to express your switch logic polymorphically because then especially in a static language at least you would be required by the language to make sure that you have all the methods implemented in Java for example. If you implement an interface and you don't implement one of the methods that's an error. You're less likely to forget to cover all the parallel cases when you introduce a new case. With straight switches you might update half the switches and then forget to update the other half. But of course that's in a static language in a dynamic language like Ruby if you subclass some abstract class and you fail to override one of its methods you're not gonna get any language warning whatsoever. So it seems like a really weak argument to me in a dynamic language. Regardless I still think polymorphic branching is a perverse idea. Mostly because I think these neatly parallel switches are actually quite rare. Much more commonly I think you have scenarios where the switches are semi-parallel like they switch over one set of factors in one part of code but then the branching logic needs to be quite different in other parts of code. So going through all this trouble to reconceptualize certain switches as being switching over types when you don't necessarily have different types that's a lot of bother and conceptual overhead for something that's quite likely to not pay off in the long run. The last example is from a talk by Robert C. Martin aka Uncle Bob who is probably the preeminent object oriented guru. And in this talk he presents a program that parses command line arguments into a more convenient data structure such as say you can query, hey, what is the value of the flag that starts with hyphen B or hyphen C, hyphen some character, right? And these arguments are specified for the schema string in this format here. They're separated by commas and if it's a Boolean it's not marked by any special character but if it's an integer it's marked with a number sign and if it's a string then it's marked with an asterisk. And so here for example this schema string expects an in flag J, a Boolean flag F and a string flag W. So given the schema if say we then had actual command line arguments of hyphen J 35 and then hyphen W blah blah blah then the flag J will have the value 35, the flag F will have the value false because there was no F flag and then flag W will have the value blah blah blah. So first he presents a version of his code which he thinks is not so great but then he cleans it up and this is that version. I won't bother showing the bad version. It didn't seem any worse or better than this better version, the ostensibly better version so I'll just show the better version. So what does he have here? First he has this abstract argument-martialer class with two abstract methods set and get. I don't know why he's using an abstract class rather than an interface because there's no actual fields here so why have an abstract class and not an interface? Whatever, that then gets extended into three concrete classes. There's Boolean argument-martialer, there's also then string argument-martialer and integer argument-martialer and their bodies are really similar. They're doing the same stuff except they're dealing with Boolean string and integer values and that's the main difference there. And then those three concrete classes are utilized in this args class which is where he has his main so I'm not gonna step through any of the codes so just look at the bottom right where we have main and so we instantiate args, we pass in a schema string and then also the actual arguments to the main function. That gets us back, this args data structure which we can then query with methods, getBoolean, getInt and getString and so here we're asking hey, what is the value of the Boolean flag L and we get back a Boolean value and so forth and then so we parse all of our arguments and get their values and then we pass them off to this executeApplication method which he doesn't actually, it's not implemented, it's just hypothetically, here's your actual program start. And notice that the args instantiation and it's getBoolean, getInt and getString methods are all called inside this try block because they can all throw args exceptions. Now, if you want, take a few moments and study this code and try and figure out what's going on. He does a few really odd things because all he really needs to do is this, here's my solution. I just have a single class args with the same main function down at the bottom, I'll show in the next slide. But first, args has three fields, it has a hash map for characters to integers, a hash map for characters to strings and a hash map for characters to Booleans and then in the constructor we pass in, just like his args, we pass in the schema string and the actual arg strings. And first what we do is we parse the schema in this loop, we split the schema string by comma and then for each element of the schema, we add that character to the appropriate hash map given the symbol character that might accompany it. And for now, we're just giving the integer and string and Boolean values in these hash maps default values. For Boolean, the default value is false but then for string and integer, the default value is null because it makes sense for Boolean flag if you omit the flag then presumably then it should be false but if you omit a string or integer flag, who knows what that should be so there's not really a default value there. In any case, our args constructor then continues and we loop through all the actual argument strings and we check first if they begin with a hyphen because if they don't begin with a hyphen we don't care about them, we just ignore them. But if we do, then we check the next letter and we see well is that in the Booleans map or the strings map or the ints map and whichever one it's in, we want to take the specified value and assign it to that character in the hash map. I understand in the default case here in the else clause if we encounter a character which we didn't see in the schema then that's an exception. You're not supposed to have any such flags if they weren't in the schema. So that constructor gets us our fully parsed data structure and then just to use the thing we have the same methods that he has in his code. We have getBooL, getString and getInt and the main function here is exactly what he had but actually what I would do is probably I wouldn't have getBooL, getString and getInt. I would just make the hash map fields public and then access them directly and I prefer explicit error handling over exceptions so the resultant code would look something like this. We get args.ints.get but that might be null and if so then that's an error. Otherwise if it's not null then we can get this value and yeah, this is a little ugly in verbose but that's mainly just because Java has an obnoxious distinction between primitives and object types so that's really the root problem here. So yeah, this is a little verbose being explicit about errors but I still think it's better to be explicit. So let's step back and look at the differences here. In my version of the code we had an args data type, it had its constructor and there was the main method and that's the entire surface level of the code. If you wanna understand what's going in my code no question whatsoever where you need to start. In Uncle Bob's code we have not just one class we have five, one of them is abstract which itself is mystifying in its own special way. He has this strange notion of marshallers. I still can't figure out what that's really supposed to mean and what's oddest of all is that these marshaller classes are all recursive. Boolean argument marshaller has a Boolean value field but then also it's Boolean args field as a map of characters to other argument marshallers. I don't know why he made it the parent class argument marshaller because it turns out in practice it's always specifically Boolean argument marshaller so I don't know what's going on there but I don't know why it's recursive to begin with it. It's very, very strange. I think what happened is that because he was programming in an object oriented style he just didn't have the clarity to see how simple what he was doing really was and so he couldn't then devise a sensibly simple solution. When someone comes to his code and they wanna make heads or tails of it they have to ponder why he has these five different data types and what purposes they really serve and then looking at the fields why are these data structures recursive and then you have to look at all the methods and everything's split up into tiny little methods. So yes, in the straightforward procedural version there's a little bit of complex logic as you can see in the loop and branching but his version just chops that up and obscures it. It scatters it all to the winds. If you think simply in terms of data and data transformation rather than trying to conceptualize your data as doers and your actions as somehow also being data types if you avoid that confusion it's much, much easier to arrive at straightforward solutions like I did where I just have this hours class with the three hash maps. Because what was the task at hand? I had this array of strings and I just wanted to convert it into a form of data that was more convenient to query. Just a form of data that was more convenient to use down the line. That's all I was doing. By avoiding all of his object-oriented nonsense we can have that clarity. This brings to mind another talk by Sandy Metz where she explicitly concedes that object-oriented code is generally less comprehensible than procedural code but she defends object-oriented code with the idea that you're not supposed to understand the entirety of your code. If you properly decompose everything into the system of little objects then you can make changes to your code by just adding in more objects without having to fully understand all the connections between them and all the interactions. Aside from being skeptical that this is actually true that you actually get that benefit in object-oriented code that you can proceed without really understanding your code I think this is fundamentally just the wrong way to program. Yes, you do need to understand your code and beyond that you need to understand your tools and you need to understand your environment meaning generally your platform. When we give up on understanding what we're actually doing when we write code it just leads to bad software.