Transcript for:
Critique of Object-Oriented Programming

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 click baity but I disagree I chose the title because I I think it's really true I have nothing nice to say about obje 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 up in the an so it's called objectoriented programming is embarrassing in this video I'll have four examples but they'll be really short uh less than 200 lines in each case uh 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 uh for each example I won't change a 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 TR pick these examples at all I just basically took the first examples I found on YouTube or Vimeo of some uh speaker giving a talk explaining how objectoriented 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 I do send in suggestions uh also I would want to follow us up with even longer examples because I know people are not going to be convinced with just 200 under lining examples they're going to say oh well object R programing virtues don't really come out until you're talking about much larger samples of code because it protects your uh 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 objector to 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 th000 or 2,000 lines and then next maybe some example that say 10,000 lines and ideally I don't know rewrite in 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 that would be a long-term project probably in the meantime if you have some suggestions of some code example you found some some gith her repo of a complete program or maybe even just some subm module 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 if it's in language that everyone knows because I I want to have it be accessible to as many people as possible so Pro probably preferably JavaScript though if it make sure it's an example that is actually in an objectoriented style not perfunctory objectoriented style but actual objectoriented 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 and 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 um 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 action 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 noun ify all our verbs it is this conflation of action with data and vice versa that I think makes objectoriented 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 a chair class for example you have to consider the true nature of a chair you have to uh 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 some principle or another of objectoriented 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 objectoriented 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 Mets 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 uh 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 and 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 uh that invokes the other three methods uh 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 disperate stuff into this 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 you know 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 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 it's you immediately dispose of it after invoking the run meth what what is this thing why does it have to be a thing it serves no purpose being a thing Sand'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 which is a sound idea I I think but we have this Constructor which the initialized method which takes in some set of options potentially uh 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 uh and from the yl file we get this data which is then uh there's some weird made a business going on here in Ruby in her defined 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 uh 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 hashmap instead of having a Constructor we have some function taking in the options doing all the same business uh parsing this yaml file but why not just return a hashmap 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 going to use again any user of the FTP download is never going to access this config propert property it's just they're going to 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 called 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 um as much as I don't like object do programming if you are going to do it I think for a very good reason you do use dependency injection CU that well that's a whole tangent but basically it solves the problem of having to uh 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 and anyway that's a Mot point because the code should just be this it should just be straight procedural code just 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 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 Constructor 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 C 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 100 functions I have 3- 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 I 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 Banis from a few years ago it's this whole series he has on object-oriented design there are 11 or so videos but this code comes from the second one of those uh to be fair to Derek his other videos on programming his more recent ones I've seen are actually quite good uh but the series is terrible because just the nature of what he's trying to teach it's just disastrous so in this example he is uh 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 uh it's not going to actually really be an interactive program it's just we're simulating the result to get the end result you know 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 uh using uml techniques uml what does it stand for unified modeling language Universal model something like that who cares it's garbage as you can see by this this is his use case he has his class diagram he has there's 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 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 A visual representation of of progression of time and and what talks to what and then from all of that he writes his actual code so we have four classes here the the fourth class is just a coin flipping game in the bottom right which is just a container for main because you know it's Java and everything has to be a class I'm not even going to walk through this code it 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 from Main and then in this main okay we 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 a loop because that's what he did uh he the user is prompted do you want to play again and you hit something and and it just goes again that's all I don't think I have to actually make the argument about why is cod is so absurd just look at it and then look at what you actually just need it to do at a minimum the lesson here is that uml is just garbage don't ever use it it's crazy if you want to plan out your code ahead of time you know do so to a degree of course just to plan out your data structures write a little bit of pseudo code don't try and diagram anything that never works it's just 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 it just 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 and why didn't you just write code to begin with cuz code has the the nice virtue of you know 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 Mets called all the little things I I don't mean to pick on Sandy it's just I found one of her talks and it was easy to find another in this talk she presents an example of some ugly code some ugly uh branching logic and she demonstrates how this ugly branching can be more cleanly represented with polymorphism so looking at this actual code um I this was not a real code example clearly I think it was an arbitary example um 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 want to see code like this if you're going to have a lot of complex logic it should be complex for a reason and and you can very quickly see in a few places that this doesn't need to be so complicated this can 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 a an empty tick method and then we create three specializations of item we have normal Bri 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 another where it's equal to a BRI and another where it's equal to backstage passes to a tefl 80 Etc 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 hashmap normal Maps the normal class H Bri to the Bri class and backstage yada yada to the backstage class and and then at the bottom we have the self. for method I think the significance of self here is that this makes it a basically a static method of guilded row it's a it's a class method rather than an actual instance method so you write gilded rows. four and you pass in the name which is going to be a string like normal and then the 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 Mak 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 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 Felts ladder the way she presents it in the talk is that polymorphism swoops in and saves the day it makes the code much cleaner but the 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 age3 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 uh 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 name name and then some object and then operate upon its members now I've seen this polymorphism can replace all 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 going to 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 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 a interface and you don't Implement one of the methods that's an error so you're less likely to forget to cover all the parallel cases when you introduce a new case uh with straight switches you might update half the switches and then forget that to the other half but of course that's in a static language in a dynamic language like Ruby if you subass some abstract class and you failed to override one of its uh its methods you're not going to 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 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 in 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 that say you can query hey uh what is the value of the flag that starts with hyphen b or hyphen C some hyphen some character right and these arguments are specified with a schema string uh 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 this schem of string expects an INF flag j a bll flag F and a string flag W so given the schema if say we then had command line arguments of um 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 I won't bother showing the bad version it didn't seem any worse or better than this better version this ostensibly better version so I'll just show the better version so what does he have here first he has this abstract argument marshaler 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 you know there's no actual Fields here so why why have an abstract class and not an interface uh whatever that then gets extended into three concrete classes there's Boolean argument marshaler there's also then string argument Marshal and integer argument Marshal and their bodies are really similar they're doing the same stuff except um 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 uh so I'm not going to step through any of the code 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 get Boolean get int and get string 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 parcel of our arguments and get their values and then we pass them off to this uh execute application method which he doesn't actually it's not Implement it's just hypothetically here's your actual Program start and notice that the args instantiation and it's get buo and get int and get string Methods are all called inside this Tri block because they can all throw args exceptions now if you want take a few moments and study his 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 there's my solution I just have a single class args with the the same main function uh down at the bottom I'll show in the next slide uh but first args has three Fields it has a hashmap for characters to integers a hashmap for characters to Strings and a hashmap 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 we split the schema string by comma and then for each element of the schema we add that character to the appropriate hashmap given the the symbol character that might accompany it uh and for now we're just giving the integer and string and Boolean values in these hash Maps default values for for Boolean the default value is false but then for string and integer the default value is null because it it makes sense for a Boolean flag if you omit the flag then presumably then it should be false but if you omit a string or integer flag uh who knows what that should be so there's not really a default value there in any case our ARS 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 boolean's map or the strings map or the ins map and whichever one it's in we want to take the specified value and assign it to that character in the hashmap understanding 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 we'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 a thing we have the same methods that he has in his code we have get bull get string and get int and the main function here is exactly what he had but actually what I would do is probably I wouldn't have get bull get string and get int I would just make the hashmap fields public and then access them directly and I prefer explicit error handling over exceptions so the resulting code will look something like this um we get ar. ins. get but that might be null and if so then we that's an error otherwise if it's not null then we can get us value and yeah this is a little ugly in verbose but that's mainly just because Java has a noxious uh distinction between Primitives and 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 want to 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 misdefined 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 marshaler classes are all recursive Boolean argument marshaler has a Boolean value field but then also it's Boolean ARS field as a map of characters to other argument marshallers I don't know why he made it the the parent class argument marshaler because it turns out in practice it's always specifically Boolean argument marshaler so I don't know what's going on there but I don't know why it's recursive to begin with 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 want to make heads or tals 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 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 up 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 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 ARS class with the three hashmaps 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 Mets 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 this the system of little objects then you can make changes to your code by just adding 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 ring 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