Object-Oriented Programming is Bad

Video Statistics and Information

Video
Captions Word Cloud
Captions
when I say that this video is probably the most important programming video you're ever going to watch it's partly because what I'm going to tell you is distinctly a minority position among programmers probably 5% or under programmers will tell you that definitively object-oriented programming is just not a good idea and in fact is going to lead you astray maybe you'll have another 20-30 percent of programmers who will hand in Hall and say that it has some virtues and some weaknesses and it might be better applied to some problems than others I'm not telling you that I'm telling you definitively no object-oriented programming doesn't fit any problem and you shouldn't take it seriously this is almost certainly not what you were told in school if you attended a programming course in the last 15 years or you read most educational materials about programming the the pervasive default assumption is just well object-oriented programming is the right way to go and it's just a settled matter so I reiterate this is probably going to be the most important video you watch about programming because it's going to tell you something you're not going to get from a vast majority of other sources first off I'm going to try and make clear exactly what I'm complaining about and what I'm not complaining about and then I'm going to try and explain well what is object oriented program really because if we don't nail that down it's almost impossible to criticize and then I'll try and account for it well if object or a mean isn't good why does it dominate the industry that's kind of an important question actually and then I'll actually get into well why does object coming toward me not work what's bad about it and then lastly if I'm telling you to not program in an object-oriented style than what you do instead what is the alternative it's called procedural programming but what does that look like exactly so what are the problems with object-oriented programming well first off the problem is really not classes per se that is I think it's actually possible to program occasionally with classes in a way that's fairly benign I don't think it's particularly beneficial but for aesthetic reasons it might seem more pleasing to have an explicit association between certain functions and certain dip types doing this pervasively though as I'll make clear is a really bad idea that's where everything goes wrong is when you try and shove every function of your code every behavior into an association with the datatype that leads to disaster secondly I don't think the problem with object-oriented programming is about performance I recommend you watch this talk by Mike Acton called data oriented design and plus he makes some very interesting points and provides some insight into that role the program in which most of us don't do but I think he over States his case fine there's a lot of software out there that should be written with much more regard for a performance but I think there's tons of software that just really doesn't apply you'll also hear complaints about excessive abstraction from generally the same people people like Mike Acton and again here I think they're overstating the case I think abstraction is actually a worthy goal in practice most abstractions aren't good it takes a long long time to develop good ones and as I'll explain a major problem with object-oriented as it does tend to produce abstractions that aren't any good that's the real problem not the idea of abstraction itself another interesting talk to watch is one by Abner coimbra called what programming is never about and the thing which he says programming is never about is code prettiness how code looks aesthetics his main point is that programmers typically focus too much on surface concerns about their code rather than stuff that really matters I think though he actually simply misstates his case or or rather his thesis doesn't really follow from his arguments which are generally valid I think when really pressed he would admit that elegance simplicity flexibility readability and maintainability structure all these things you might file under code aesthetics I think you admit actually do matter but I think the more accurate way to spend his point is that these surface level virtues of code are good things and actually important but object doing and programming and abstraction heavy programming in general fails to deliver them in fact it provides just the illusion of these things object-oriented programming is sold on the basis that it supposedly provides these things but particularly simplicity and elegance it actually makes things worse lastly be clear that I'm pushing procedural programming not necessarily functional programming which is a different thing as I will make clear in a moment I happen to think that functional programming actually is the future of higher level code I think it may actually be the default way we program at a higher level in ten years from now or something but there are serious efficiency problems that make functional programming not really viable in certain domains of programming and so my message is whether your code ends up functional or imperative that's a separate matter we're go allas your code should be procedural rather than object-oriented so it's a good time now to make clear exactly what are the competing paradigms of programming that we're really talking about there are four main possibilities your code confers to be both procedural and imperative procedural meaning that you have no explicit association between your datatypes and your functions your behaviors an imperative meaning that we just mutate state whenever we feel like it we don't have any special handling of shared State which can cause problems as your code gets larger and larger and more complex but in procedural and imperative programming we just cope with the problems as they arise and you can think of this style of programming as being basically the default it's the the obvious way to get work done so this is really how all programming was done in the early days of computers but then starting in the 60s as programs got more more complicated people began thinking about well how do we solve this problem of shared state because it really can get out of hand and so we got two major prescriptions on how to handle the problem one of these prescriptions says that our code should be procedural yet functional meaning that all or most of the functions that make up our code should be pure they should not deal with state and so programming in the style we would tackle the problem of shared state by minimizing state trying to get rid of as much of it as possible the other prescription people came up with said that our code should be object-oriented and imperative and the strategy here is that we simply segregate our state we take the state that makes up a program and instead of sharing it promiscuously we try and divide and conquer the problem we package it into these encapsulated units that we call objects and objects contain other objects and so forth and that's how we conquer the problem and these two prescriptions are actually orthogonal to each other we can do both the functional business to minimize the amount of state which our program deals with and then whatever state is left over we can then segregate into separate units of encapsulation and in fact I think this combination approach may actually be the ideal way to structure programs at least in terms of high-level code where we don't care so much about efficiency as I'll explain I think segregating state is actually a valid strategy up to a certain level of detail certain level of complexity and so if we first minimize the amount of a twitch our code deals with it then becomes a viable strategy to segregate the Romanian state you may have noticed in my definition of object-oriented I said nothing about inheritance and that is because inheritance is simply irrelevant no one defends it anymore even people who advocate for object-oriented well very very commonly these days tell you to be very very careful in using inheritance or maybe not to use it at all and so it's not really pertinent to any argument about whether object-oriented programming is good or bad for similar reasons I didn't say anything about polymorphism in my definition because polymorphism really good or bad isn't exclusive to object-oriented programming you can have procedural code that is polymorphic and in fact even more polymorphic than is typically available in most object-oriented languages so it's really not part of the discussion as far as I'm concerned when I complained about object-oriented programming I'm really complaining about one idea encapsulation encapsulation does not work or as I should qualify this encapsulation does not work at a fine-grained level which is the core of what object-oriented ideology prescribes that we need to take the state of our programs and divide and conquer that problem by chopping it up into tiny little pieces that is the nature of object-oriented code and it doesn't work it leads to madness before delving into why object-oriented programming doesn't work it is important to address this mystery of well if object-oriented isn't so great why does it now dominate the industry and why is it done so for almost last twenty years I've heard it sometimes suggested that well this was an imposition of management management wants interchangeable developers so that we can have a cookie cutter assembly line development process hence business types were really enthusiastic about object oriented programming promises about code reusability and compartmentalization it's a theory that sounds plausible to me but the main sticking point is that object-oriented programming doesn't actually deliver these promises you'd think people would have noticed sometime in the last 20 years yet they seem not to have noticed I'm also skeptical of the idea that management actually really inserted themselves in these technical decisions that often I suppose once a object-oriented and that became the pervasive norm then yeah sure management would push towards doing what everyone else is doing so that they can draw from the larger talent pool but otherwise aside from pushing engineers to just go along with the legacy system and not rebuild everything I just don't think that many business managers really care that much about technical decisions I'm much more inclined to think that object-oriented programming is something that programmers did to themselves and the question is then well why I think a big part of the answer simply comes down to Java when it was first introduced in the mid-90s Java seemed like a welcome reprieve to many programmers compared to the alternatives Java seemed really simple for example on the PC this is what application development looked like you had to use the win32 api and see and not only that programmers have to concern themselves with memory management as you do and see but on top of that win32 just doesn't feel like the C that you would learn from books that's not what you would learn from knr it's not what you would learn in school it's all this excess and macro heavy stuff on top that is really mystifying even the tools you would use to write C programs on a Windows platform the the visual studio tools you know wouldn't be the same as what you would learn in university probably where you probably had a UNIX system and that's what you learned so it was already this platform with a quite high barrier to entry but then also in this period it was undergoing this ugly transition from win 16 to win 32 and so you can begin to see why programmers were desperately looking for some way out the only real alternative at the time in PC programming was what Visual Basic but that was another effective Microsoft platform you're locking yourself in too and I suppose otherwise you might use peskow or Delfy but that platform had its own issues and so it shouldn't be too surprising that when Sun Microsystems came along and said here's this free thing that everyone can use across all platforms that got people's attention and Java had other things going for it it certainly seemed more accessible just in terms of like its naming conventions for example you look at the Java API s and you see things like file input stream which is not cryptic at all yes there are definitely issues and how abstracted many of the api's are and you know having to derive base classes to use the API as and all that nonsense but on first glance on surface inspection it certainly seems like a friendlier system it's not like UNIX where you have stuff like ioctl which is you're supposed to know is input/output control and other really horrible abbreviations and then when 32 had the same thing you know LPC TST RS stands for Oh what's long pointer to a Const teach our string so even if you know what a t char is and a long pointer is you're stuck in this world where everything is cryptically abbreviated and it's just as goddamn puzzled that you have to figure out at every step Java came in and said no we don't necessarily have to program that way we can write real programs that don't have to be horribly cryptic in that way and then Java took things too far in the other direction but that's again we'll get to that Java also smartly had the C like syntax the curly brace syntax so superficially at least it seemed familiar to programmers from C and C++ and it seemed like real programming it has curly braces after all and then the whole compilation to VM bike code business was again very alluring to programmers trying to escape their platform headaches and then Java also offered some very basic niceties like proper name spaces without header files for Christ's sake we still have to deal with header files to do our real programming in C and C++ at least 20 years after we should have ditched them if for this one thing alone I think it's worth giving Java some credit it mainstream programming without header files and then of course also very alluring garbage collection I know some hard core low-level programmers out there will insist the garbage collection is never necessary it's never a good idea but whether or not that's the case it's really hard to argue with the appeal it shouldn't be surprising that the vast armies of people doing business crud applications wanted to stop thinking about memory management Java also mainstreamed exceptions as the primary way to handle errors and whatever problems this may have in practice I think it definitely seems appealing because the alternative is ugly the alternative is what we do in C and C++ of having to have an inbound err return value or likes you know saving to global and checking the global after everything you call it's not pretty going with a little return and that style probably the better way to go but that's not the solution Java came up with and so normalize this other thing that seemed better at the time I think some people also came to like the subject-verb-object nature of method calls over straight function calls because well this is just what we do in English its subject first then verb than object I myself don't find it all that appealing I prefer consistency and I think the distinction between subject and object in many many cases gets very very which is one of the problems with object-oriented programming as we'll get to but the style of syntax in Java led to this convenience people I think since then have become addicted to which is in their IDs it offers them for this data type what are my options what can I do with this thing it seems to enable Stella programming where you can just sort of browse you don't have to hold all the options in your head you just have a vague notion of way I'm going to take that thing and transform into this other thing I don't remember exactly what the methods called we'll just grope my way there using Auto completion in my IDE again there's really actually no reason you couldn't have the same style of convenience in a purely procedural language you just have an auto completion for given this first argument what functions take this type as its first argument and a being effectively the same thing really but because of quirks of history and syntax design this particular editing convenience has been implemented for languages like Java but generally not straight procedural languages and I think method auto-completion may actually largely explain why people sometimes claim that object oriented API is feel easier to use it's because you can largely autocomplete your way through most of the usage another thing Java is seem to have going for it is that back in the mid-90s this was the heyday of GUI programming and it seemed really logical to map components as we see them in a GUI window and classes in an object-oriented program that seemed like a very natural correspondence this was the most tangible version of the real world modeling which object-oriented promised at the time it seemed like a very plausible story and on top of that you have the virtue of Java Bean supposedly cross-platform with the Java swing API so you can write gooeys that will run on any system they'll look horribly ugly but at least hey they run on everything you could do so-called rad rapid application development of GUI applications like you do in Visual Basic except in Java you're not locked into Microsoft's platform so the funny thing to me about Java is that I think in an ultimate history it could have had virtually all the same success if not even more perhaps if it weren't object-oriented at all it could have just been a straight procedural language and would have had still a big long list of attractive selling points we could have had all the same portability the same garbage collection same exception handling and so forth down the line without any of the object oriented miss or at the very least without forcing everything into the mold of classes you could have it of a language like Python say where there are classes but also just straight procedural code if you want and they can live side by side just fine so this still is this question Java aside there seems to be some appeal to object oriented programming in itself inand what is that well I think very simply if you go back to the 60s and 70s as people were grappling with the problems of software systems getting larger and larger people tried to identify units of code abstraction that were larger than individual functions and data types it's natural to want to describe any complex system in terms of large scale components you know if you talk about human anatomy that you don't explain it first in terms of microbiology that would be nuts we first talked about very major organs like the brain and the heart and the kidneys and so forth as software gets larger and larger it felt like these units of code we were building out of the base materials these data structures and functions they became smaller and smaller relative to the whole sadly though the one general answer people could come up with of what is a unit of code abstraction bigger than a function and bigger than a data type is just simply a combination of the two and hence objects were born we took our functions in our data types and we associated them together into these larger units we want to think in terms of paragraphs rather than individual sentences and object-oriented programming seem to have an answer for how we could do that it's also very natural that as we build larger and larger systems and complex things as much as possible we want simple rules to guide us object-oriented programming seem to present a unit of abstraction and a set of guidelines whereby we can incrementally accrete larger and larger systems this line of thinking is what led us to patterns and then the so called solid principles and dependency injection and test-driven development and all this stuff which has subsequently been piled on by many people who insist that this is now the one true way to do object-oriented programming but to me all these best practices represent band-aids they are compensation for the fact that the original vision of object-oriented out and every few years there's a new ideology in town about how we actually do object and programming for real this time it's very easy to miss this dynamic I know I did for several years because I think within all of these addendums to object marry programming there there's lots of mystical speech dancing around genuine insights but it's not quite cohesive object when your programming feels like this circle which we've been trying to square for over a generation now finally let's talk about what's really wrong with object-oriented programming specifically encapsulation which is the linchpin of the whole thing so consider what is an object an object is this bundle of encapsulated state and we don't interact with the state of that object directly all interactions with that state from the outside world come in through messages messages to the object the object has a defined set of messages which it will receive called its public interface and so we have private information hidden behind the public interface when an object receives a message it may in turn send messages to other objects and so we can conceive of an object-oriented X all communicating with each other by sending messages many people today forget though that the original conception of a message is not exactly synonymous with just a method call yes in practice it means calling methods but a message strictly speaking sends only copies of state it doesn't send references a message sends and returns information about state not state itself and while wait a minute objects themselves are state and this has some interesting consequences it means that strictly speaking messages cannot pass around object references I've never seen a Java or C sharp code base that ever follow this rule perhaps some small talk programs have but in general this rule is not observed at all and probably for good reason as we'll discuss but anyway if we take the role seriously it means then for an object to send a message to another object the first object must hold a private reference to that other object because otherwise how is it going to talk to it to talk to knob jekt you have to have a reference to it and where is it object going to get a reference to another object if it can't get object references from messages the references which an object needs have to all be there at the object's inception they have to be there for the whole lifetime of the object and there's an even deeper consequence which is that if an object is sending messages to another that other object is part of the first object's private state and by the principle of account halation an object should be responsible for all the objects which it sends messages to this should be obvious if you consider that messages indirectly read and modify state when B sends a message to a here it's messing with the state of a indirectly shirt but is still messing with its state and so what happens when other objects come along and send messages to that same object what's happening here we have shared state it's hardly any different than if you had a single global variable being shared by say ten functions if you have an object receiving messages from ten other objects those objects are all effectively tied together because they're implicitly sharing this state sure the interactions with that state are indirect through public methods but those methods are providing very trivial kinds of coordination of the state you can impose rules through the access or methods like saying oh if you access this field it's a number well you can only increment that number you can't mutate it in any other way fine but it's a very trivial kind of protection the hard problems of shared state are much much deeper where in the system of ten objects all sharing the state is the real coordination and the answer is there isn't any as soon as you have objects being shared encapsulation just flies out the window so if we're taking encapsulation seriously the only real way to structure a program to structure our objects as a graph is not as a free-form graph but as a strict hierarchy at the top of our hierarchy we have an object representing effectively the whole program it's our god object and that has its direct children which represent the sub components and those children in turn have their own sub components and so on down the line and each object in the hierarchy is responsible for its direct children and the message is being passed strictly only ever go from parent to their direct child the god object here for example is not supposed to reach down to its grandchild it has to do all of its interactions with this grandchild indirectly through the grand child's parent otherwise who really is responsible for that object who is managing its State supposed to be the direct parent and so what happens when we have some sort of cross-cutting concern like down in the hierarchy it turns out oh wait there's some business that that object has with another object and a totally different branch of the hierarchy how do they talk to each other well not directly everything has to go through their common ancestor for a to send a message to be here it can't actually directly invoke any kind of method it has to mutate its own state and way and then information about that state that new intention of the object gets returned from a message sent from aids parent days parent intern same thing has to happen so it gets back up to the common ancestor and then only finally when we get to the common ancestor can that intent be realized as a series of message calls but not directly down to B it has to be bucket-brigade it down through the hierarchy that is how you handle cross-cutting concerns in a strict encapsulated hierarchy obviously no one writes programs this way or at least no one writes whole programs this way and for good reason is an absurd way to have to write your code now you might argue that people do follow these principles in practice they just do so inconsistently and perhaps there is some value in a code base where you apply these principles inconsistently perhaps half-assed encapsulation actually gets us something so imagine we have some sort of freeform graph of objects making up a program and we decide oh well there's this subsystem of objects that together should be their own self-contained encapsulated hierarchy of objects and so we're going to refactor our code well very often what that means is not only do we have to do a lot of complicated rethinking of the structure of the relationships here of what calls what on the other objects we very typically have to introduce more objects like say here to represent this whole new subsystem we probably have to introduce some new sub god object you know some ruler of this subsystem now all interactions with the subsystem have to be rican sexualized as going through this minor deity so say we successfully do this refractory and now while our code doesn't follow the principles of encapsulation perfectly it's doing so in a half consistent way and maybe there's some benefit there well I think what tends to happen is subsequently we decide oh wait we need some new interaction between elements of this encapsulated subsystem and instead of having to do the hard work of figuring out how exactly it all gets coordinated from the roots of that subsystem the temptation is to just handle the business directly but if we want to do the proper thing we have two options that maybe it turns out that that stuff external to the subsystem actually just needs to get integrated into that subsystem and so it comes under the purview of the subsystems root but otherwise we now have two subsystems that need to coordinate and who's going to do the coordination well now we need a new subsystem god responsible for the collective business of these two subsystems and now all interactions of these two subsystems have to go through this root object but also all interactions with the outside world and these two subsystems have to go through this new root object so as you can see chances are really good that what you would actually do is say it and just do this you would just reach in and have the objects directly interact with each other whether they should properly do so or not and now where is the encapsulation what's the point whether you follow the rules strictly or loosely you're in a bad place if you follow the rule strictly most things you do end up at being very unobtrusive these entities tends to be very abstract and nebulous but alternatively if you follow the rules loosely what are you even getting why are you bothering what is the point when I look at your object-oriented code base what I'm going to encounter is either this over-engineered giant tower of abstractions or I'm going to be looking at this inconsistently architected pile of objects that are all probably tangled together like Christmas lights you'll have all these objects giving you a warm fuzzy feeling of encapsulation but you're not going to have any real encapsulation of any significance what people tend to create when they design object-oriented programs are overly architected buildings where the walls have been prematurely erected before we have really figured out what the needs of the floor plan are and so what happens is down the line turns out oh wait we need to get from this room over here to that room over there but oh wait we've erected barriers in between so we end up busting a bunch of holes through all the walls like the kool-aid guy and the resulting pattern is really not organized at all it's just Swiss cheese we thought we were being disciplined neatly modularizing all the state but then the requirements changed or we just didn't anticipate certain details of the implementation and we end up with a mess the lesson we should take from this is to be very careful about erecting barriers about imposing structure it's actually better to start out with a free-form absence of structure rather than impose a structure that will likely turn out to not really fit our problem bad structure that doesn't really fit our problem not only makes it harder to implement the code in the first place that hinders change and it confuses anyone who looks at our code because it's implying one thing but then what's really going on is another in the object-oriented world we have to think about all these graphs we have think about an inheritance hierarchy we have to think about a composition graph we have to think about data flows between the object and also we're thinking about a coal graph the liberating thing about procedural code is there's just the coal graph we also of course do have to think about how our data is structured and how our data gets transformed throughout the course of the program but the beauty of procedural code is that we can think about that totally independent of any notion of responsibilities when I'm looking at my data I can think just about my data and when I'm looking at my functions I'm not thinking about all these self-imposed barriers I'm not constantly trying to group and modularize everything into these small units of so-called single responsibilities when I sit down to write object-oriented code I always have to play this game I have this mental list of the obvious data types which my code will deal with and have the separate mental list of all the imagined behaviors I want in my program although the functionality I imagine it to have and then what object-oriented ideology demands is that I take all my behaviors and I somehow associate each one with one of my data types inevitably what this means in any non-trivial program is I'm actually going to have to introduce all sorts of additional data types just to be these containers for certain behaviors which otherwise don't naturally fit with any of my obvious data types the data types I knew I actually wanted because they represent actual data I need in fact as programs get larger and larger in object-oriented code it tends to be that these unobvious unnatural data types tend to actually predominate you end up with majority of so-called data types which really aren't there because they're representing data they exist simply as a text to conform to this ideology about code modular ization very quickly we end up in what Steve Yeager called the kingdom of nouns where every aspect of our program has to be rican Sepp Shu alized as not just mere standalone verbs you know functions you have to be Rican sexualised as nouns things that represent a set of behaviors and so what we get in our object-oriented code bases are all these service classes and manager classes and other what I call Dewar classes these very nebulous and abstract entities even when dealing with data types and behaviors that are relatively concrete which have fairly visible connections to the functionality apparent to actual uses of the program even here the matchmaking game constantly presents us with these obnoxious philosophical dilemmas in object-oriented analysis into design we constantly have to ask ourself stupid questions like should a message send itself because maybe instead we should have some sender object which sends messages or wait a minute maybe there should be a receiver object which receives messages or a connection object which transmits messages so very quickly the real world model in which object oriented programming promises becomes a fool's game where there aren't any real good answers in my experience object oriented analysis and design very quickly becomes analysis paralysis if you take the ideology seriously as I did you're going to waste a lot of time hemming and hawing about to conceptualize these elements of your program object-oriented programming is generally sold to students on the basis of these trivial examples that neatly model real-world taxonomy but what we get in practice from object-oriented analysis and design is a lot of very abstract excess structure with no obvious real-world analogues note here that programmers have their own peculiar definition of abstract when programmers talk about abstraction they're generally talking about simplified interface over complex inner workings what's odd about this is that a more general usage abstract as a connotation of being hard to understand something which is abstract has no resemblance to the things of common daily life and it turns out that most things which programs do are abstract in this sense and so it shouldn't be surprising that we have great difficulty conceptualizing the components of a typical program in terms of neatly self-contained modules particularly modules which have any real-world analogue when we pollute our code with generic entities like managers and factories and services we're not really making anything easier to understand we're just putting a happy face on the underlying abstract business and for every excess layer of abstraction or getting more abstract NISS in attempting to neatly modular eyes and label every little fiddly bit that our program does we're actually just making our program harder to understand something that happens all the time when I look at object-oriented code bases is that I'll try and find the parts in code that correspond to some user visible functionality but trying to find the functionality going by clues from the names of classes and the names of methods tends to be very misleading very typically my expectation that shanell DX would be in the class named X turns out to be wrong because the abstract nature of what we typically do in programs generally necessitates that functionality is not going to be self-contained it's not going to neatly fit into one neat module and so the class which is called X will very superficially relate to X but then all the real work is done elsewhere scattered throughout the code this makes me question what is the value of having a class called X if it doesn't really contain all the business of X what this class X really represents is actually misleading code structure and how is that helpful how is that conducive to understanding of your code base the other reason I have this problem reading code bases and trying to track down where functionality actually lives is because object-oriented design tends to fracture functionality in our code it tends to take what otherwise could be relatively self-contained code and split it up into many separate methods across many separate classes typically often in many separate files for God's sake this fracturing is accepted because of an ideology about encapsulation and this notion of classes and methods properly having so-called single responsibilities and our certainly valid arguments for that idea certainly it is much easier to get a small short function correct and to get a large sprawling function correct but the important question is that in splitting our code under too many little small methods and too many separate classes are we actually decreasing the total complexity of our program or just displacing the complexity just merely spreading it around in either case there is this attendant trade-off we're making whereby splitting up larger units of code into many smaller ones we're greatly increasing is the so-called surface area of our code where I come along and I look at your code base and try and get a foothold and everything split up into these tiny little units these tiny little packets of code reading this kind of code often feels frustrating in the same way it can be frustrating to eat a bunch of little candies that are all individually wrapped and when all your methods are really really short you end up having to jump all around the code to find any line of logic a lot of business that otherwise could be neatly sequentially expressed in longer methods gets artificially split up so it feels like you've taken a neatly sorted deck of cards and thrown them into the air so you can play 52 card pickup so if you're not going to be writing object-oriented code what are you going to be doing instead you're going to be writing procedural code but what does that look like well as I mentioned at the beginning this doesn't necessarily mean you need to avoid classes entirely if you have a language like Python or C++ where you have both straight functions and also classes there are some cases where the association between your data types and certain functions is really really strong that it fits some organizational purposes to just explicitly associate them together but making those functions methods of that type the most obvious example would be ADT so abstract data types things like queues and lists and so forth the key thing to keep in mind however is at the moment you start hemming and hawing about whether this particular function really has a primary association with that data type that should be the moment you say screw it will make it just a plain function because it turns out that most things we do in code tend to be cross-cutting concerns they don't have necessarily special obvious relationships with particular data types they might concern more than one data type and that's why you should generally prefer functions so you don't have to play this silly game of matchmaking functions two data types so we're going to be writing our code primarily out of plain functions and we're not going to attempt to encapsulate the state of our program at a fine-grained level because it doesn't work however shared state is still a problem and if we're not careful it can get out of hand we can't totally solve the problem unless we do a pure functional programming but short of that there are broad guidelines we can follow to mitigate the problem first off when in doubt parameterize this means that rather than passing data to functions through global variables you should instead make that data and explicit parameter of the function so it has to get explicitly passed in as much as possible we want data access in our program to flow through the Col graph so any time you're tempted to pass data to a function through a global because it seems more efficient or maybe just more convenient you should give that a strong reconsideration secondly whatever Global's you do end up with in your program it can be slightly helpful to group them logically into data types even if this means you effectively have a data type with one instance in your whole program this little trick can often make your code seem just a little bit more organized in a sense you're just using data types of way to create tiny little sub namespaces but if you do a good job logically grouping your Global's this way as a side benefit this can complement rule number because now you can more conveniently pass this global state to functions by a bundling your data together in two types you typically cut down on the number of parameters which functions have to take though do be careful there is an art to how you logically group things together the third guideline is to opportunistically favor pure functions even if you're not explicitly working in a functional style or working in a functional language if you see an opportunity to make a function pure it's generally good strategy to take that opportunity again pure functions tend to come in at efficiency cost but the brilliant thing about pure functions is that they're the only truly self-contained unit of code when I'm reading and writing a pure function I don't have to think about anything else I can just consider that function entirely unto itself therefore they tend to be easier to understand and to make correct the fourth guideline is that we actually should try to encapsulate our code only in a very loose general sense at the level of namespaces packages modules whatever your language has so when I'm working in go Lang for example I think of each package is having its own private state and then a public interface I find that encapsulation at this coarse grain level tends to work because you're typically dealing with much larger units of code than the supposedly ideal classes of object-oriented programming the typical golang program is going to have not that many packages maybe like ten at the high end and structuring I'm your handful of elements into hierarchy of encapsulation is reasonably doable when it turns out during development that oh wait I have some cross-cutting concern of my packages and so we're going to violate this perfect hierarchy of encapsulation again it's not such a big deal because you're dealing with a relatively small group of objects all the basic problems of encapsulation are still there is just that the coarse-grained macro level the problems are reasonably manageable the last guideline is that you shouldn't be scared of long functions for a long time now program and students have been advised to when in doubt chop their code into smaller and smaller functions but doing this has significant costs there are trade-offs it turns out that most programs have these key sections where most of what the code is doing is a long laundry list of stuff and what we're told to do in these scenarios is write functions like this where all the business has been extracted out to separate functions the problem with doing this pervasively is that what was naturally a logical sequence of code and was otherwise written in sequence top to bottom is now spread and out of order throughout your codebase obviously in cases where the business extracted to a separate function is something that you want to call in multiple places that's a very good reason to have a function but if all these functions were just called in this one place I would generally prefer looking at code where the business of those functions is just done in line and if you want high-level documentation of what's going on in my func here then you just put what I would call a section comment denoting what each section of the code does in this arrangement the sequence of the business is totally clear and when I'm browsing the whole codebase when I'm looking outside this function there's less clutter because there are now fewer functions have to look at and wonder well hey where is that cold I wonder what that thing does it also has advantage of letting us avoid having to named functions naming stuff is really important in code but is really really hard to do well and in general I find it preferable if we can avoid naming entities as much as possible in this arrangement we don't have to think hard about what to call these functions we can just have a comment line and have a full English sentence which generally is better at conveying accurate meaning and also is simply easier to write if for whatever reason it doesn't seem adequate to simply comment a section rather than extract it to a separate function the next best thing is to make it a private function a nested function such that it's clear this function is not called anywhere else it's only cold within this function in this arrangement I as a reader of your code coming from the outside I'm still presented with a smaller surface area fewer entities in the code and so it's just easier for me to get a foothold now when you do write functions which are hundreds if not thousands of lines long you still should keep in mind general guidelines about code readability basic things like not straying too far from the left margin for too far or for too long you know you don't want to have code that's indented in eight levels because it gets really obnoxious to scrolling up and down code if you have to scroll over for one thing and also it tends to just imply there's a lot of busy logic in this part of code and it gets confusing so likewise you also need to look out for parts of functions where the logics is getting too complex the first thing to do is of course to try and simplify your logic but failing that there are going to cases where hey we should just split this off into a separate function so it's more neatly self-contained complexity the other concern with longer functions is that as your function gets longer and longer you tend to accrue more and more local variables so what you want to do is hopefully a language allows us you want to try and constrain the scope of the local variables so that they don't exist for the full duration of the function but rather for subsections this way either reader of your code when they scan up and down the function I don't have to think about all the variables for the whole duration of the function the way this is done the most curly brace languages is you can just introduce a new sub scope with curly braces so here for example this integer X variable only exists within these curly braces when you have subsections of a function which you are commenting it's generally a good idea to when in doubt enclose them in curly braces this gives readers of the function and assurance that variables from the preceding sections don't fall through to the following sections and so in later sections we don't have to think about the variables that were used above where possible the even better thing to do is to enclose these local scopes in their own anonymous function that's then just immediately called an advantage here within the nested function is that it's not just its own sub scope but also within this anonymous function it's guaranteed then any return is not going to return out of the enclosing function it'll return just out of this enclosed function and so we have a stronger guarantee that the logic of this subsection is self-contained from the enclosing function unfortunately what I often really want when creating subsections of longer functions is a feature that doesn't exist in any language I know of it's an idea I've only seen in one other place it was Jonathan Blow and this talks about his argument language these making and the idea is that we want something like an anonymous function which doesn't see anything of its enclosing scope the virtue of extracting a section of code out to a truly separate function is that everything that comes into the function has to be explicitly passed to a parameter it would be great if we could write inline anonymous functions with the same virtue specifically what I would propose is imagine we had a reserved word use that introduces a block and in the header of the eu's we list variables from the enclosing scope which we want to be accessible in this block but otherwise anything from the enclosing scope would not be visible these listed variables however would really actually be copies of those variables so if you assign to X or Y here in the scope you're assigning to x and y the local variables of this use block not to X and y of the enclosing scope which is the effect you get with a truly separate function right you assign to the parameters of function you're not modifying what was passed to the function you're just modifying those local variables that's the same thing we want in this use block furthermore a use block should itself return values so you use return inside the use block and it doesn't return from the enclosing function it returns from the use itself that uses an expression and so we can return values from this use and assignment to this variable a so in effect we'd have this block of code which is as neatly self-contained as a separate function however it is written in line and so it's very very clear that oh this is a piece of code that's only used in this one place you don't have to go look for it elsewhere and also we don't even have to give it a name instead we can just put a section comment header before the use block and that is generally much better for containing the actual intent of this block of code if later down the line we decide that this block of code actually should be extracted to tone proper function that's a very easy thing to do you can have an editor convenience that does that for you automatically it's already clear what the parameters and the arguments should be all the programmer would have to do is provide a name for the new function so anyway it would be nice if language has had this feature unfortunately I don't know of anyone that does but regardless you shouldn't be so scared of long functions they actually have their place in most code bases at the very I hope I can get you to try procedural programming it doesn't really matter what language you're in if you're in Java or C sharp you can write the procedural code you can break the rules but if you've ever felt any of the paralysis that I felt attempting to do object oriented programming properly to square the circle I think you'll find abandoning all those ideas and just reverting the procedural code to be a liberating experience I can tell you from personal experience that having read these books that you don't need to read them they don't have answers they're not going to square the circle and you're going to waste productive years of your life trying to live up to their ideals I'm not saying there's nothing to these ideas there are bits and pieces that have value test-driven development for example has some interesting ideas there's value in testing but that's part of the problem is that kernels of good ideas have been taken to holistic extremes in a way that I think has been disastrous for the industry and certainly for programming education there are very few solid holistic answers about how we should write code we'd all be better off if we stopped chasing that Chamara you
Info
Channel: Brian Will
Views: 932,463
Rating: 3.9597459 out of 5
Keywords: OOP, Object-Oriented Programming, programming
Id: QM1iUe6IofM
Channel Id: undefined
Length: 44min 35sec (2675 seconds)
Published: Mon Jan 18 2016
Reddit Comments

The downvotes indicate one of two things:

  • People disagree with his opinion - they love OO and can't handle being challenged
  • People thing his argument is bad

I'm in the second camp. I actually agree with his overall point - that OOP is overused, and that other approaches are more appropriate in many cases. I agree that procedural code is not only perfectly valid, but even more appropriate than OO code in a lot of situations. But everything in his "Why does OOP not work" section seems to hinge on the idea that you can't include object references in messages... which as far as I can tell, is just a completely made-up requirement.

Even Erlang, which embodies shared-nothing message passing perhaps best of all, allows object references (in the form of process IDs) to be passed in messages.

He further argues that an object is solely responsible for its own collaborators, so an object must instantiate its own dependencies and (due to the first made-up rule) must never share them with anybody else. This reduces the object graph to an object tree, and he goes on to show how ludicrous that sort of system would be. He justifies this with the statement "The moment objects are shared, encapsulation flies out the window".

Wait, what? Where does that claim come from? So, if I pass a single logger instance around in my code, I've thrown encapsulation out completely? If I have one thread-safe, mutable list that is written to by multiple producers, I'm breaking some sacred rule and not doing "true" object-oriented programming?

The sharing of objects is a technique for managing and limiting the use of shared state. A given scope (function or object) can only access the shared state if it's been explicitly given a reference to that shared state. It's reasonable, and indeed common, to have functions that act as coordinators, instantiating objects and deciding which of those objects is to be shared with which other objects. Objects share state because somebody with a broader view of the world decided that they should share state.

I think the speaker tried to make too strong a point. If he had stuck to "OO Programming isn't the universal solution to all problems", I think his point would be readily accepted. But he reached too far, and tried to make his more general argument "OO is the wrong solution to nearly all problems". But to argue that point, he had to argue against a made-up and limited notion of what OOP is - he made a strawman argument.

I would have liked to see more concrete arguments. Show the kinds of state-sharing problems that OO encourages or exacerbates, and talk about how non-OO approaches avoid or resolve these problems. Don't argue from theory, argue from practice.

I actually agree with most of what he said... except for basically everything in the "Why does OOP not work" section, which appears to be the central point of the overall presentation. OK, I'd also argue that long functions should be broken up; if you feel a need to put section headers in a long function body, that's a smell, and there's a decent chance that those sections could be extracted into supporting functions with no loss of readability.

👍︎︎ 22 👤︎︎ u/balefrost 📅︎︎ Jan 19 2016 🗫︎ replies

So this post is getting a lot of downvotes and I don't think it's fair. He makes a number of very important points.

I remember when Java first came out and he is absolutely right on why it was adopted so eagerly. It never proved itself better than the 40 year old patterns that everyone used, it was because Java had so many features and libraries built in to the SDK and because of Intellisense.

Anyone who's worked on large object oriented systems can see the cluster fucks that can occur. Procedural programming has it's own cluster fucks, but OOP is not immune from them.

👍︎︎ 25 👤︎︎ u/umilmi81 📅︎︎ Jan 18 2016 🗫︎ replies

Oh boy, do I hate titles like this.

👍︎︎ 30 👤︎︎ u/[deleted] 📅︎︎ Jan 18 2016 🗫︎ replies

Regardless of how right or wrong the anti-OOP stance may be, I think it is very healthy to have the entire paradigm publicly challenged like this. I think OOP carries a lot of value, but it causes problems when left unchecked. So, it's actually refreshing to hear that good software is written without obsessing over perfectly encapsulating every bit of data in the program.

I have developed the following observations after many years of pushing through OO code. I'm not presenting them as indisputable truths. They're just how I feel so far.

  • Inheritance is a red flag. I see it done wrong far more than I see it done right.
  • Class member variables should either be all public (a struct basically) or all private (data to uphold the overall object). Any other mixtures are questionable at best. protected opens data up and encourages inheritance.
  • Many small classes can be reduced to functions. People like to make objects out of tasks: Download, Thread, etc. I grew tired of always having to make-and-invoke. It made more sense to just call a function to kick everything off. I can async it if I want to; I don't need every single class to be clever about its usage.
  • Code reuse is an admirable goal but often stops code from just being useful in the first place. OOP overly pushes the idea that code needs to be "ready for anything". It is definitely worthwhile to ensure that code has as few dependencies as possible, but people need to know they're taking it too far when the code stops serving the very purposes it was created for.
  • OOP overly promotes self-awareness. Programmers want to be able to call obj.get_parent() or obj.get_neighbor() or obj.get_container(). This results in horrifying dependencies/hierarchies. It is much better to have higher level managers: container.get_neighbor(obj)

I'll add more as I think of them.

👍︎︎ 29 👤︎︎ u/TheBuzzSaw 📅︎︎ Jan 18 2016 🗫︎ replies

TL;DR OOP is never the right answer. "Encapsulation does not work at a fine-grained level". So why did OOP take over? Java with GC, Namespaces/No headers, Exceptions and other reasons. OOP leads to essentially shared state. Structuring OO programs is really hard and leads to a mess. Instead, write well organized procedural code, preferring pure functions and using ADTs when appropriate.

👍︎︎ 9 👤︎︎ u/WalkerCodeRanger 📅︎︎ Jan 18 2016 🗫︎ replies

Uh... TL;DR the major points for those of us at work right now? (It's a 45min video...)

👍︎︎ 8 👤︎︎ u/Existential_Owl 📅︎︎ Jan 18 2016 🗫︎ replies

Several questions broken into different threads here:

Honest question here: is unit testing just out in this approach? That always seemed like the biggest plus to breaking out functionality into smaller functions. I mean, you could just break up the functions into "subfunctions" as the video seems to suggest for organization and to have the same automated testing functionality, but the video seems to suggest "just shove everything into the god function" is better, and my immediate reaction is "what the hell are you smoking and where do I get some". I get his argument about that usually meaning tracking functionality across multiple files, etc. being tedious, and I agree with that, but having dealt with god functions in various places, I've always found them less maintainable than the alternative.

Am I missing something there?

👍︎︎ 3 👤︎︎ u/i8beef 📅︎︎ Jan 19 2016 🗫︎ replies

The proposed use construct:

a = use x, y {
    // ...
    return 3;
}

can be implemented with C++11 lambdas:

a = [x, y]() {
    // ...
    return 3;
}();
👍︎︎ 3 👤︎︎ u/JavaSuck 📅︎︎ Jan 20 2016 🗫︎ replies

I have been refactoring some code I have into one gigantic data struct(2k lines struct and 6k fields, AKA god object) plus functions that take either portion or entire struct by pointer. It turned out to be quite more manageable compare to smaller data structures spreading all over the code in hundreds different places.

A portion of programming became designing that data structure and minimizing dependencies, since it is a lot easier to see what references what in the program by finding all occurrences of pointer to a particular field, this led to simpler and shorter program. The functions became easier to reason about as well, a function that takes the entire struct is clearly more complicated and perhaps unnecessary, a function that takes a field of the struct containing a reference to other field is potentially problematic.

The important take away for me is program complexity is dictated entirely by data complexity not code. OOP encourages mixing of data and code to the point of making it impossible to visualize the data.

👍︎︎ 3 👤︎︎ u/[deleted] 📅︎︎ Jan 18 2016 🗫︎ replies
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.