Programming Vexations Part 11: The Schism

By Shamus Posted Thursday Nov 21, 2019

Filed under: Programming 51 comments

There’s a bit of a schism in the world of programming. This divide isn’t over a single issue, but instead over a sort of emerging design philosophy that tends to cluster around particular ideas. It’s complex and multifaceted, and you could spend an entire book exploring all the various differences. In the broad strokes people talk about it in terms of being a debate between people who favor object oriented (OO) programming and people who favor data-oriented (DO) programming, but that’s mostly shorthand for a lot of competing ideas.

A couple of years ago I wrote about object-oriented programming, and the criticisms people have with it. Since then, that discussion has grown louder and more complex. What I’m about to outline is a simplification of this ongoing debate.

Yes, there are other programming styles / philosophies besides these two, but let’s ignore them for now because they’re not really relevant to game development.

The Schism

Ironically, the goal of the project is to build a system that keeps people from falling into the hole.
Ironically, the goal of the project is to build a system that keeps people from falling into the hole.

OO design is a design philosophy that dictates how your code should be structured / conceptualized, while DO design is a set of priorities that says what your code should do. A DO programmer might use OO design patterns to solve their DO problems, so these two camps aren’t necessarily at odds. And of course people participating in the debate are often ignoring the all-important context of the domain they’re working in. A user interface programmer will insist that OO design is the way to go, and an embedded systemsIf you’re working on embedded systems, you’re designing control systems for hardware rather than writing something a user interacts with directly. Think drones and comm satellites. developer will claim that OO is madness and DO design is the only sane way to go, but in truth they’re both using systems that fit their particular job.

For the purposes of this discussion, I’m going to refer to these two sides as Object People and Data People, because the OO and DO acronyms look silly. There’s no way I could read the previous paragraph aloud without snickering.

Here are the ideas and attitudes that Object People cluster around:

  • Strong support for Object Oriented coding style. (I mean, obviously.)
  • The compiler is your friend.
  • Maintainability and readability are everything. Well-written code should be beautiful.
  • It’s all about the code.
  • If you want to improve your work, you need to better understand design patterns, templates, and coding styles.

While the Data People are more likely to favor these ideas.

  • Strong support for the notion that there is no single coding style for all tasks. It all depends on the problem you’re trying to solve.
  • You can’t blindly trust the compiler because it won’t always do what you expect.
  • Performance and stability are everything. Well-written code should work.
  • It’s all about the data and the hardware. Your goal is to manipulate the data with the hardware, not to separate the two with layers of abstractions.
  • If you want to improve your work, you need a better understanding of the hardware and the data.

Again, I want to stress that these things aren’t always mutually exclusive. Data People want beautiful code and Object People want code that works. I’m taking this noisy, scattered gradient between these two extreme positions shoving everyone to one end of the spectrum or the other. I’m well aware that you can’t sort all programmers into two buckets like this. However, in the interest of not cluttering this up with additional digressions and qualifying asterisks and teasing out all the nuances of this stuff, I’m going to pretend that we live in a two-bucket world.

An Object Person might say something like:

Object Oriented design allows you to create code that maps to real-world structures and relationships. Your program can have a SpaceMarine object, and everyone reading the code will instantly know what that is. The SpaceMarine can hold an object called Gun, and the Gun object can have properties that can be immediately understood.

This design provides a double benefit. One, it allows the designer to express solutions in a way that  matches the problem they’re trying to solve. Two, it allows anyone maintaining the code later to understand the structure of the program based on the objects it uses. Another coder can come in and intuit the existence and purpose of SpaceMarine->Gun->Shoot () without needing to painstakingly scroll through all the code.

To which the Data Person might reply:

The metaphor suggested by your class structure is a lie, and it keeps you from reasoning about what the program is doing. Your SpaceMarine isn’t actually carrying a Gun because in terms of data, there’s no such thing. Your “gun” is a combination of three completely different data structures:

  1. The shared gun model that gets rendered. There’s only one copy of this and it never changes.
  2. The collection of behavioral characteristics that define a particular type of firearm: Reload speed, fire rate, kick, accuracy, etc. These attributes might change from one type of gun to the next, but all SpaceMarines carrying the same type of gun will share a common copy of this data.
  3. A set of data describing what a particular SpaceMarine is doing with a particular gun. This includes what animation is playing, how many bullets are left, the attachment location at the SpaceMarine’s hand, etc.

You’ve taken these three totally different concepts and mashed them together. Your code is promising that SpaceMarines carry guns like in the real world, but the reality is that SpaceMarines technically “share” a single gun. If another coder tries to modify the gun for a particular marine, they’ll be modifying the global gun used by everyone, so the code is actively misleading by pretending to work like the real world.

You’ve mixed the gun location data in with gameplay data, so now the positional data needed for rendering isn’t packed together in memory, which means you don’t have a good way to render all the guns in a single batch. This stupid metaphor is costing processor cycles and preventing you from thinking about what the computer is actually doing when it runs your code.

The Object Person would counter that no, these things aren’t a problem if you do proper Object Oriented design and the system works fine if you know how to do it “right”.

The Data Person will counter that regardless of what the hypothetical “right” way is, this confounding and useless way is how the system is taught.

Then the Object Person will claim The Data Person clearly hasn’t ever bothered to learn the thing they’re trying to critique. The discussion quickly spirals out of control from there. I think you get the idea.

The best example of the Data camp is this talk by developer Mike Acton: CppCon 2014: Mike Acton “Data-Oriented Design and C++”. Acton is opinionated and brusque. By assuming that all programs need to push the hardware for optimal performance, he engages in a lot of the domain tunnel vision I talked about at the start of this series. Having said that, he makes a lot of important points about how being ignorant of the fine-grain details of your data and hardware can lead to huge performance penalties.

I don’t think this is a battle between right and wrong. I think this is a battle over suitability. When you’re trying to transform megabytes of dataIn this case “transforming data” is just high falutin’ coder talk for “make shit explode, die, fall over, reload, or otherwise change”. as a result of interactions between thousands of objects from a dozen different chaotic systems and you have a very narrow window of time to work with, then you need to be very focused on your data. You need to think about what needs to be done to that data, how it’s arranged in memory, how long the transformation will take, and how you’ll present the result to the graphics hardware.

On the other hand, not every program needs this fanatical attention lavished on trivial blocks of memory. The classic example is when you’re building a GUI interface. Slider, buttons, input boxes, and check boxes all lend themselves really well to an object-oriented approach. The program often spends most of its runtime idle, waiting for user input. In these cases, those layers of abstraction can take a lot of the burden off our poor programmer. They don’t need to fuss over data structures or police compiler output to make sure it didn’t do anything destructive. If raw performance isn’t your top priority, then you can focus on making pretty, comfortable, easy-to-maintain, self-documenting code.

Games Programming is Kind of Both

The thing is, a video game exists in both of these realms simultaneously. Sometimes you’re talking to the graphics hardware and you need direct memory access and a complete understanding of what will happen when the code is run. Sometimes you need to pick a random number for bonus headshot damage and you don’t care if you waste a couple of processor cycles on needless precision.

If I’m dealing with the graphics hardware or manipulating thousands of particles, then I want to get close to the metal and write something that looks a lot like raw old-school C. On the other hand if I was handling interface stuff or gameplay mechanics, then I’d want to express myself in a comfortable language like C# where I can leave all the heavy lifting to the compiler.

Vexation #5: Adhering to Dogmatic Design Principles is not Programming.

Shamelessly swiped from Reddit. Click to see the thread. Unlike StackOverflow, some of the replies are pretty good.
Shamelessly swiped from Reddit. Click to see the thread. Unlike StackOverflow, some of the replies are pretty good.

Java is a good example of a “big idea” language. It’s specifically designed to support and enforce object oriented design patterns. You can’t just make some code to play a sound. Playing a sound is an action, and so you need that action to be owned by an object. Instead of PlaySound (), you create a SoundPlayer class, you create an instance of that class, and then you order it to do what you want in the form of SoundPlayer.Play ().

This problem where actions are owned by objects leads to the somewhat absurd Kingdom of the Nouns. There’s nothing inherently wrong with this style of coding, and I’ve run into many situations where this style felt really good. But in Java, you’re not allowed to deviate from it. You’re forced to use object oriented design, even if that design doesn’t suit your project. You have to figure out how to express this verb / action using a noun / object design. You end up writing containers for actions and creating all these extra classesNot to mention the source files to hold them. and bloating code just to adhere to object-oriented orthodoxy.

Likewise, Haskel is a “big idea” language. It’s built around the idea of functional programming. Functional programming is designed to solve the problem of cascading state changes that I talked about back in part 2. Consider a line of code like this:

Player.Gun.Fire (aim_location);

That seems trivial enough. But then you run the program and encounter a situation where shooting the gun created a bullet that blew up a red barrel that created a spherical damage zone that generated particles and sounds and damaged a support pillar that triggered a physics event. That one line of code makes massive changes to the state of the world. These changes are varied, numerous, and extensiveIn the real world, you’d have some sort of event-based system that would wait until the gun was done being fired before kicking off any of those other systems. But explaining how that works would take a lot of page space, and this example is good enough for illustrating the problems of expanding state change..

Functional programming doesn’t allow objects in the program to make changes to each other. A bullet can’t damage a bad guy. Instead, there needs to be some sort of master object that owns all the bullets and bad guys. When a collision happens, the owner says to the bad guy, “Here’s a bullet. Shoot yourself with it and return a version of yourself exhibiting the result.” So then the owner throws away the old version of the bad guy and replaces it with the version that’s been shot.

This means you’ll never have any surprise side-effects and things will theoretically be easier to debug. However, this style of programming is very tricky and has a large performance overheadI imagine the need to create a “new” SpaceMarine every frame to replace the one from last frame means there will be a lot of churn in memory allocations. But I’ve never done this sort of coding so I can’t say for sure..

I want to stress that Java and Haskell are not bad languages because of these restrictions. These languages were designed around these ideas on purpose, and faulting the languages for sticking to their core design is somewhat missing the point. It’s like complaining that the low blade clearance on a band saw makes it tough to cut bread. There’s nothing wrong with the tool, it’s just not the tool you should be using for this job.

(Of course, then you come to the problem where your workplace forces you to use the wrong tool. That sucks, but that’s a problem with people and not the language.)

Having said all that, strict object-oriented design is a bad fit for gamesAlthough informal OO design is fine. Objects are fine, as long as you’re allowed to deviate from OO when doing low-level stuff., and functional programming even moreso. I know I’ve spent a lot of time in this series fantasizing about what a modernized language for GameDev might look like, but the limitations of hardware mean we’re still bound by the Old Ways. We’re still going to need occasional direct memory access and unguarded access to memory. A GameDev language needs to be forward-looking, but it also needs to hang onto the 1972 way of doing things.

 

Footnotes:

[1] If you’re working on embedded systems, you’re designing control systems for hardware rather than writing something a user interacts with directly. Think drones and comm satellites.

[2] In this case “transforming data” is just high falutin’ coder talk for “make shit explode, die, fall over, reload, or otherwise change”.

[3] Not to mention the source files to hold them.

[4] In the real world, you’d have some sort of event-based system that would wait until the gun was done being fired before kicking off any of those other systems. But explaining how that works would take a lot of page space, and this example is good enough for illustrating the problems of expanding state change.

[5] I imagine the need to create a “new” SpaceMarine every frame to replace the one from last frame means there will be a lot of churn in memory allocations. But I’ve never done this sort of coding so I can’t say for sure.

[6] Although informal OO design is fine. Objects are fine, as long as you’re allowed to deviate from OO when doing low-level stuff.



From The Archives:
 

51 thoughts on “Programming Vexations Part 11: The Schism

  1. methermeneus says:

    I recall Jon Blow talking a lot about functional programming. His points can pretty much be boiled down to, “This is not a paradigm that can carry an entire game, but if you can program functionally, you should, so it should be easy to do when you run into a place where functional programming is better.”

    1. Wolle says:

      John Carmack made a similar point in this Gamasutra article.

      1. Amanda says:

        The pure functional way to append something to a list is to return a completely new copy of the list with the new element at the end, leaving the original list unchanged. Actual functional languages are implemented in ways that make this not as disastrous as it sounds, but if you do this with typical C++ containers you will die.

        I giggled. Carmack’s writing is delightful to read.

    2. Echo Tango says:

      [X] should be easy to do when you run into a place where [x] is better.

      This is one of the overriding design goals of Jai. The other one is that it should be easy to switch between X and Y, if X and Y are logically very similar. The original example is something like working on the colors of enemies, and switching from one color variable, to an array of colors, to calling a function that returns an array of colors, to realizing you needed a genericized helper-thing (SuperOptimizedList or whatever) that contains your colors. Depending on the language, those three things re-order the positions of your enemyColor label, the brackets, and maybe change one of the sets of brackets entirely. Jonathan is writing Jai, so that your label (enemyColor) always is on the left, then the data type, then brackets if it’s an array on the right, etc.

  2. GargamelLenoir says:

    “Unlike StackOverflow, some of the replies are pretty good”

    I’m not sure we’re seeing the same website.

    1. Ancillary says:

      All depends on the language you’re querying about. I dunno; maybe the C++ community is a little more smug and a little less forgiving than most, seeing as how the language has been around for a while.

      1. Gresman says:

        That is my experience as well.
        Java, SQL and C# are quite helpful and friendly.
        But certain section of C++ feel like elitist Dark Souls comments and Linux comment. Meaning somewhere between abusive unhelpfulness (e.g. Read the f manual!/Why don’t you know that.) and absurd unhelpfulness (e.g. Why don’t you use these specific compiler options? or On my specific machine with my homebrew kernel it works.)

        But these observations are quite anecdotal and come from someone who once got told the answer “Read the book “The C++ primer”” after asking the question “Could you please explain this error message to me?”

    2. Noah says:

      Shamus cares a lot about tone – for instance, he wrote a whole article about how terrible the classic “wrong question” piece is (“Pounding a Nail: Old Shoe or Glass Bottle?” / https://weblogs.asp.net/alex_papadimoulis/408925).

      StackOverflow is often a maze of valuable information, confounded by aggressive tones, “this is stupid” responses and unhelpful bureaucracy like how they handle “duplicate question.” With a “duplicate” it’s often not particularly obvious where to go next (scroll down to find it.) Closed dupes often still contains some useful information that wasn’t transferred to the original-ish question. And questions marked dups aren’t necessarily perfect duplicates of the other question – subtle differences are often rounded to “same.”

      It is *incredibly* valuable to have a place to ask and answer questions, and StackOverflow users do a lot of that. However, the design of the site itself is often at odds with them doing so in a useful way, and the hostility of the community adds an extra unnecessary cost on top of the activity.

      1. Echo Tango says:

        Originally, StackOverflow was designed, so that people were supposed to actually answer the question-as-asked, first and foremost. Comments are supposed to be for asking clarification, and any “you ought to do X instead, or have you considered Y” was supposed to go at the end of you answer, after the clearly-marked answer to the question-as-asked. Unfortunately, many people using the site don’t follow the guiding principles, and my co-worker informs me that the community is rife with politics, admins acting with overreaching force and without understanding or considering the nuances of questions or answers, … as you said, the community is kind of hostile.

        I still think StackOverflow’s the best tool we’ve got for a lot of this stuff, so the only way to fight these things in my opinion, is to lead by example, :)

      2. Joshua says:

        That is such a weird post, although this was noted back when Shamus originally posted about it. It seems way too tone deaf and socially clueless to be sincere, but too weak to be satire.

        “But I think the problem is worse than not having enough grumpy curmudgeons: in my experience, forum moderators actively delete critical posts that don’t solve the problem , keeping only the hacks that solve the hacks. When new users come to read the posts in the future, they may actually think, “hey, I could use this to solve my problem.”.”

        The bolded part seems to be satirical and trolling anyone who might agree, but the remainder seems to be a sincere statement.

    3. Olivier FAURE says:

      Stack Exchange is great when your question relates to a subject that has already have a vibrant community for a few years; in that case, most of the questions you’ll come up with will have already been asked and found multiple thoughtful answers, some concise and some detailed. (or if you just want to learn more about a subject in general)

      It’s kind of a toss-up if you have a new, original question, though.

  3. Steve C says:

    you can’t sort all programmers into two buckets like this.

    You can. With Blender.

    1. DerJungerLudendorff says:

      But will it Blend?

  4. Robert Conley says:

    I work with embedded systems for metal cutting machines, have to write traditional graphics intensive software to allow people design parts for said metal cutting machine, and then have to add in a data centric aspect because customer what what they make tracked and reported correctly to accounting.

    I manage it through a system of interfaces. And interfaces are what get lost in the whole debates. Each of the three areas are coded in the manner best suited for the domain however they passes around data and commands through specific interfaces. The part designer design is object oriented, the embedded systems code is welded tightly to the hardware, and the data centric parts are coded with framework used for database applications.

    Interface are similar to a class definition in object oriented language except they don’t define behavior. Instead the module or object implements the behavior when declared as using that interface. A well designed interface described how that aspect of the system can be controlled or what can be queried. How it works inside can be tailored independently of other modules or object implementing interfaces.

    This gets lost because C++ screwed up interfaces. Instead in the early days they focused on behavior inheritance that a dog is a subclass of a mammal which is a subclass of an animal. As it turns out the usefulness of behavior inheritance is limited however the usefulness of interfaces is far greater. One just has to look at at Design Patterns by Gamma, Helm, etc to see that most of the patterns use interfaces not inheritances. Which why it was useful to me even though I was using Visual Basic 6 where inheritance can only be done by compositing objects but handle interfaces well.

    For example the gun for spacemarine example. The model for a gun is a labor intensive resource. If you can’t have a new gun model every time you make a new gun with different stats, then you need to put the 3D model associated with a gun behind a interface. The code behind that interface manages the pool of gun models in a way that performance is not impacted. If Bob add in a new gun type, the gun requests its model from the interface. The code behind the interface makes sure that the request is handled in a way that minimize performance. Or when the proposal is review there is one spot that can be reviewed to evaluate the impact of adding one more gun model.

    The interface can be tailored for the requirements. Sometime, I have interface being called all the time to retrieve data. Sometime I call an interface just once, it returns the data in a form that I store within the calling object. In turn that pass that around to whatever needs it. This may be needed for performance when you know regardless of what the code is doing behind the interface, the request is time consuming.

    What useful is that I write the interface in a way that documents how it interacts. If things change, I change the interface which immediately flags everywhere it is used. Forcing me to evaluate the impact of the interface changes. It also useful for unit testing as in your test frameworks you can put test stubs if hardware or connections are not available in the test environment.

    1. John says:

      I like Java’s interfaces. Does an object need to be able to respond to keyboard input? Then implements KeyListener. Mouse input? MouseListener, MouseMotionListener, or MouseInputListener, depending on whether you care about clicks, movement, or both. You can make a JFrame (the Swing API’s version of a window) that listens for keyboard or mouse input, or, if it makes more sense to do it another way, some other object that listens for keyboard or mouse input in that JFrame. It’s very flexible and very convenient.

      1. Daimbert says:

        The OO response would be “I’d rather EXTEND a KeyListener class and then only have to implement the functions I want, and allow for the default behaviour for the ones I don’t care about”.

        And then the interface response would be “And when they change that default behaviour to something you don’t like?”.

        And so on.

        1. John says:

          I’m not entirely sure what you mean by “only have to implement the functions I want” unless it’s that you resent being forced to include something like


          @Override
          public void keyTyped(KeyEvent e) {// Do nothing}

          in your sourcecode. I confess it irks me a little, but it’s not that big a deal.

          Anyhow, speaking as an amateur/hobbyist, I’d rather extend a class by implementing an existing interface–i.e., one that ships with the JDK or is included in some other library–than extend a class by writing code that replicates that functionality myself. In other words, I know enough to implement KeyListener but not enough to write a class that does what KeyListener does.

          1. Daimbert says:

            There are two big issues with it:

            1) I want to see if the code that uses the KeyListener interface is right for me, or if I should use the code that uses OtherKeyListener interface … or, heck, if I should just write it all myself. So, I try out KeyListener, and then all of a sudden I have to implement all of those methods in some way, even to do nothing, just so that I can try it. I wouldn’t have to do that if I was just extending KeyListener; I’d only have to implement methods that I want to play with to do what I want them to do.

            2) I definitely want to use KeyListener, but for keyTyped I want the standard behaviour that always happens when a key is typed. In general for interfaces I’d have to implement it anyway, and then implement it to the standard behaviour. And then if I wanted to use that interface again in a different class that can’t inherit from the previous class I’d have to do that again. If I was extending a standard KeyListener class, then I wouldn’t have to do that. Of course, I could do that myself but then what’s the interface doing other than letting me hook into someone else’s library that has decided it wants things as an interface?

            So it’s less just having to add do nothing methods — which can be annoying itself — but also the implications of doing that. If interfaces are overused, it becomes incredibly annoying.

      2. Echo Tango says:

        One thing I like about Go vs other languages, is implicit interface implementation. i.e. Users of some object[1] decide if they want to use it to satisfy some interface they’ve defined for themselves[3], and the creator of the original thing doesn’t need to write a public interface[2] if they don’t want to, and they don’t have any implements SomeInterface to worry about.

        [1] Technically, Go doesn’t have objects, but “object” is faster than typing “data structure with attached functions”.
        [2] Explicit “interface” type. There’s the implied “interface” of what functions on your struct you’ve made Public vs private.
        [3] I usually start writing my interfaces when I remember I need some tests.

    2. Daimbert says:

      I had to deal with interfaces in Java at some point, and I think this sums up why I ended up loathing them, especially if they are overused:

      What useful is that I write the interface in a way that documents how it interacts. If things change, I change the interface which immediately flags everywhere it is used. Forcing me to evaluate the impact of the interface changes. It also useful for unit testing as in your test frameworks you can put test stubs if hardware or connections are not available in the test environment.

      In Java, at least, the compiler enforces that anyone that implements an interface must implement every method specified in the interface. What this would mean in the case above is that any part of the code that uses your interface suddenly has to go and implement a method if you want to add a method or set of methods to the interface. They must do this even if you are adding that method to support functionality that they don’t want and would never want. With the inheritance/behaviour based model, you can avoid doing this to them by adding a method with a “Do nothing” behaviour in the Base Class and then only force them to implement it if they want the functionality. You can also define a standard behaviour in the Base Class and only force others to actually implement a method if they want to change that behaviour, making it far easier to add new functionality without forcing everyone else to deliberately add it.

      That being said, interfaces do work for cases like yours, where you have different areas and want to force them all to conform to a specific interface, but can’t specify a default behaviour for all of those different domains. Another case I can think of is for message processing, where you define all the callbacks that they need to handle for a message and keep that list to the bare minimum. Any case where you want to say “If you want to use my framework you must let me ask you this and do something intelligent” is an excellent one for interfaces. But it doesn’t replace behaviour-based polymorphism, which you often really do want.

      1. Robert Conley says:

        My opinion that interfaces that have a lot of unimplemented methods were not properly designed and need to be refactored to rework this. For my own work I set up the basic elements back around 2002. Over 17 years I refactored several interfaces to simplify or to better fit the problem because now I have a dozen things implementing them while before I only had two or three.

        For example the interface for controlling plasma cutting torches got refactored twice as I gained experienced with more models and new features came out that had to be supported.

        The issue I have with frameworks, like Java, is that they have to be Swiss Army knifes. So I tend to take whatever framework I am dealing with and write my own interface with only what I need. Where that happen along the API Chain depend how critical acting in real times. With the part designer, data, least critical and the communication with the motion and cutting device hardware the most critical.

        The biggest issue with gaming is that any framework (or programming issue) has a strong tendency towards being a swiss army knight because gaming projects are usually one and done. You may use the underlying code for a run of two or three game but then the hardware and the game itself changes enough that you have to start over again.

        My situation is that I deal with machinery that need to last decades. The software I am charge in a direct line of refactorings from the middle 80s which each framework/language being used for a decade. Plus while we have a lot of customers it is a drop in the bucket compared to AAA release. So if there a rough spot there was time for us to iron it out. Other than quality the main concern was not to do anything to jeopardize our policy of software support for the life of the machine.

        We started on HP 300 series Graphical Workstations and we had function based interfaces although didn’t call them that as object orientation didn’t exist. It was dumb luck that our initial framework was readily adaptable to object and interfaces by the late 90s. But it did make it a lot easier than some of the report of other conversion we were seeing because we had clearly documented (in code) interactions between the different sections and sub sections of the code.

        When it came to dealing with Microsoft Windows and the nuances of the API we hid that behind interfaces. Which helped when switching over the .NET framework. Our first test with the .NET framework with graphics didn’t go well especially with the 3D modelling. Performance was bad. But because it original and the new were behind interface we could continue with the rest of the conversion/coding until we found the algorithm that had the performance we need.

        1. Daimbert says:

          Well, if you can refactor code that you see is getting stuffed with cruft then things always work out better [grin].

          For my job, I work on an in-between product: it takes years to get customers to take it up and once they start using it they will keep using it for years. Products that customers will still pay for have been 20 years old, even though we’ve had to create new products since then. So you don’t throw it away quickly. But it’s also pretty large and varied and can have multiple dependencies across groups and features. Add to that the fact that no one wants to introduce new bugs and that we often have to hit short deadlines for features to get paid, and you have a system where people stuff things in at the last minute and no one wants to refactor code and risk breaking and force retesting the entire thing.

          So if you add things to an interface but then decide that things should be refactored or split up, you can run into the issue that either you then would have to update all of the code that is using that interface to conform to the refactored code, or else you have to try to get them to do it and have to have the political clout to get them to add it to their schedules when they may not see the benefit. We’ve had a number of framework turnovers recently and the issue always comes up, and if the architects have the political clout then the feature groups all grumble about this extra work, often at the last minute, that they have to do. And if those rearchitectures cause issues or cause features to miss deadlines, the architects start to lose political clout and start to get ignored.

          For organizational refactoring, arguably regular OO is better because you can shuffle things around, especially at higher levels, without really impacting what happens below. But it still can be a pain … and doesn’t work for those cases where an interface really IS the better solution.

          Ultimately, if you can take the time to write really well-designed code AND have the ability to rewrite swaths of it if circumstances change, pretty much any model will work. The issues always come in because people don’t have the time to write really good code and don’t have the time to change it when things change.

      2. Uristqwerty says:

        I believe that Java 8 added the option for interfaces to have default method implementations, as they wanted to extend parts of the standard library that were extensively implemented in third-party code.

        1. Daimbert says:

          I think so as well, but I’m not sure that’s actually good practice for interfaces, and brings some of the annoyances of regular classes back in when most people push interfaces for avoiding those problems.

  5. Abnaxis says:

    Am I the only person who likes and appreciates Stack Overflow, and genuinely finds it very helpful?

    1. John says:

      I’ve had more luck browsing Stack Overflow than I’ve had asking questions there. About half the time I end up figuring out the answer to a question an hour or two after I post it and then I feel like an idiot for posting in the first place. Maybe it’s the questions I ask or maybe it’s the way I ask them, but I don’t typically get many useful answers or, heck, many answers period. I did once get an extensive and honestly quite lovely answer in which the answer diagnosed my problem as concurrency-related and introduced me to Java’s Optional class . It was extremely thoughtful and I appreciated it tremendously. It totally failed to solve my problem, however. I figured out the answer on my own–the sound clip didn’t repeat because I called the dispose method too early–about a day later. Browsing Stack Overflow, on the other hand, is sometimes actually useful to me.

      1. Kylroy says:

        “I’ve had more luck browsing Stack Overflow than I’ve had asking questions there.”

        I think that’s very, *very* much intentional.

        1. Abnaxis says:

          I mean, I’ve had a couple of questions get moderated out, but that’s kind of just from newbie mistakes. I feel like most suggested edits I’ve ever had were pretty well justified, and once I got the hang of it most of my questions were answered in a timely manner with very good answers, even the “this is a language I am unfamiliar with and it’s not doing what I think it should” questions

          As someone who has spent A LOT of time reverse engineering others’ code SO has been invaluable

    2. Shamus says:

      Joking aside, StackOverflow is invaluable. But it also has its share of annoying blowhards, and they earn the site a bad reputation.

      1. I have to deal with them on rpg.stackexchange.com. I have a much more liberal view of what consistent a proper tabletop roleplaying questions. I don’t try to debate but just use my vote against closure or try to open certain questions. But since there more of them active then of me, my efforts are often fruitless. Several times they messaged me about being “contrary” and working against stack exchange. My reply is “I have my vote, I will use it accordingly, have a nice day.”

    3. Retsam says:

      I also like StackOverflow, and am slightly active on there. I think there’s a couple things to point out about StackOverflow:

      Most interactions on StackOverflow – even ones where questions get closed or answers get removed – are perfectly cordial. However if someone has 10 interactions and 9 of them are good and 1 of them are bad, they aren’t going to be talking about the good ones. This dynamic applies everywhere – every site has it’s jerks – but it’s a bigger issue on StackOverflow where the crowd-sourced, point-based moderation powers mean that some of the jerks have moderation powers. (Though actual “Moderators”, with the real powers, are elected)

      StackOverflow tries to balance two priorities: being useful for people asking questions right now, and being a maximally useful as a repository of previously answered questions. These two priorities somewhat conflict, and I think when they do conflict, they often favor the latter, which I think makes sense – the vast majority of the use of StackOverflow is for looking up answers to already answered questions, not asking new ones. To this end, there’s a huge focus on improving the quality of existing answers – it’s encouraged to update answers (including other people’s answers) either to fix mistakes or when circumstances change.

      A lot of frustrations come from policies aimed at improving the site’s role as a knowledge-base: such as the anti-duplicate policy. Yes, often a “duplicate” question will still have some unique features compared to the “original”, but from a knowledge-base perspective, having too many slight variations on the same question is not a good thing – I’d rather have 5 good answers on 1 question than 20 good answers spread over 10 questions (or, worse, 100 questions), and a smaller number of questions means that community efforts to improve answers are more focused.

      For a lot of question – especially basic “I’m new to this language/technique/tool” – I think you’re better off going elsewhere than StackOverflow – there’s a lot of language-specific and tool-specific Discord/Slack/Gitter channels (StackOverflow itself has chatrooms) out there where you can get help – and I don’t think that’s a criticism of StackOverflow; their goal is just to be a knowledge base more than a support line.

    4. default_ex says:

      I like Stack Overflow when I am still relatively new to a given language. I grow to avoid it as I learn a language. Rarely has that site been helpful once I’ve gotten comfortable enough with a given language and API to understand how it works internally. At that point, forums tend to be much better about helping you with anything you get stuck on.

  6. Cubic says:

    Regarding functional programming, most attempts have used a hybrid approach where you also can do some updating. Crash Bandicoot (written in GOAL, a Lisp dialect) was a successful example.

    https://all-things-andy-gavin.com/2011/02/02/making-crash-bandicoot-part-1/
    https://www.gamasutra.com/blogs/DaveBaggett/20131031/203788/My_Hardest_Bug_Ever.php
    https://www.gamasutra.com/view/news/300400/How_Naughty_Dog_broke_Sonys_hardware_rules_to_create_Crash_Bandicoot.php

    For another example, a 3D modelling tool written mostly in Erlang, Wings3D: https://github.com/dgud/wings

    If I had to make a guesstimate, I’d say you can performancewise use Erlang in the same gaming situations as Python, at least. Maybe writing MMORPG servers in Erlang would be nice given its properties.

    1. tmtvl says:

      Wow, that hardest bug ever article is amazing.

  7. Cubic says:

    I’d characterize the ‘data crowd’ as being more like ‘frontier people’. They want to wring performance out of their system right now and they live in a world where tools and platforms can be rough. On the other hand they won’t have to support and extend this system to the point of unrecognizability for another 20 years. Instead they will move on and build a new one ASAP.

    On the other hand, the ‘OO crowd’ want to manage complexity and provide (relatively) easy understanding, extensibility and reuse, perhaps without worrying so much about the short-term platform. Paying a bit of performance to attain those goals is okay because you win elsewhere.

    The wise man of course tries to use these respective strengths where best applicable.

    1. Mistwraithe says:

      A good way of putting it. I’ve mainly worked on business software which needs to be maintainable so the goals promoted by Shamus’s Object People resonate most with me.

      I think that these days game programmers should be putting a bit more focus into maintainability given than games as a service has been growing massively.

  8. King Marth says:

    Data allocation in functional languages actually isn’t as bad as you might think, precisely because of the immutability guarantee – anything that wasn’t changed can be reused and shared across multiple instances because you’re not allowed to alter those bytes in place. There’s only one “5” which everyone can use, if you take 5+1 then you haven’t changed what 5 is, you’ve just started looking at 6.

    By nature, this sort of optimization relies on you not having specific expectations about the layout of bits in your cache lines. It does naturally produce the sort of data optimization in the example though, with the 3d model being shared by virtue of not being modified rather than knowing you can’t modify the 3d model because it is shared.

    1. Retsam says:

      Yup, this is called a Persistent data structure.

      In some ways it’s *more* performant than mutable data structures – like the extreme example being that making a copy of a data structure (which is a more rare operation in a mutable-style, but still happens) is a relatively expensive operation normally, but completely free with persistent data structures.

      But yeah, I think the main performance killer is just how high-level functional programming languages inevitably are, which prevents a lot of the optimizing techniques you’d use on a low-level language.

      And specifically with Haskell, a lot of the weird performance comes from quirks of its lazy execution model. In Haskell, values aren’t computed until they’re needed, which in some cases is a performance optimization (not computing extra, unneeded data), but in many cases, it’s a penalty. It’s a neat idea – it lets you solve problems with techniques like “construct an infinite array, then take the first five elements”, which would obviously not work if Haskell actually attempted to construct the infinite array – but it does make optimizing Haskell somewhat non-straightforward.

      Not that it can’t be done; here’s a really interesting (if somewhat clickbaity title) blog post on writing a highly optimized `wc` in Haskell. But it is a bit of a weird system (and not fundamental to the concept of functional programming).

      1. Cubic says:

        I’m not an Haskell expert by any means but it always seems like you have to code around the original key feature, laziness, and then write some pretty odd code to get good performance. On the other hand, the ghc implementation is capable of taking you pretty far once you know how to use it.

        (You might still ask yourself if all those academics spending all those man-years — dozens? hundreds? — should have started out somewhere more convenient. How far would they have come by now?)

        1. Retsam says:

          From what I’ve seen, you don’t actually code around laziness, you just need to add a few annotations to tell the complier where not to be lazy. The hard part is just the expertise of knowing where those annotations need to go.

          And I do think Programming Language Theorists work on languages like Haskell is directly beneficial – a lot of the ideas in “toy” languages are eventually adopted into more mainstream languages. Particularly in recent years, we’ve seen a lot of functional programming concepts bleeding into more mainstream languages:

          Java adopted the Streams API which is straight out of functional programming, the concept of futures/promises(and the related concept of `async/await`) has been adopted by many languages, Go’s often-praised concurrency model is based on an idea called Communicating Sequential Processes, and Rust is particularly functional-programming inspired, with algebraic data types, and it’s “trait” system which was directly inspired by Haskell. (I was going to put links to the relevant wikipedia pages, but last time I had too many links my comment never made it out of the spam filter)

          Some features that haven’t yet landed in mainstream languages, but might someday include ideas like Dependent Types (where the type system can include concepts like the length of an array), and algebraic effects. (And certainly many others; I don’t actually have any formal study of Programming Language Theory)

          1. Cubic says:

            Good point re: academics bringing the state of the art forward. I might still argue that laziness was one of the ideas that didn’t work out so well in practice, but there have been a lot of useful spinoffs.

            (Note that futures and CSP were developed in the 1980s, much like lazy functional programming actually, so it may be a while before today’s ideas get into the mainstream.)

          2. Olivier FAURE says:

            On the other hand, Rust really shows how convenient the programming concepts can get once you start focusing on expressiveness over strict mathematical purity.

            Also, Rust’s async/await + Result implementation proves that Monads as they are understood in Haskell (and other functional languages) are pointless garbage. Fight me.

            1. Retsam says:

              Why would Rust implementing two monads in a pragmatic way prove that the concept in Haskell – a language with very different purpose and usage – is “pointless garbage”? (I mean that question both rhetorically and non-rhetorically: I don’t really understand the argument you’re making)

              Personally, I like the unified concept of a “monad” and that by giving a consistent interface it allows you to use the same operators and functions in a variety of situations: it’s nice that you can use the same operators and syntax to handle async behavior, empty values, potentially erroring operations, stateful operations, parsing, side-effects, etc. And I think the general idea that these concerns can be separated from the main logic of the code, is a good one.

              But overall, people make too big a deal about monads, I think. If you understand the usefulness of Result and Option and Future individually, you don’t need to understand that there’s a conceptual framework that can treat them all as versions of the same thing – but can be useful, too.

              1. Olivier FAURE says:

                Mostly what you said abut people taking them too seriously. Like, sometimes you’ll find FP enthusiasts going “Your language has Result and Option and Future and that’s nice, but does it have monads? No? Guess you’re not really doing functional programming, then”.

                Personally, I like the unified concept of a “monad” and that by giving a consistent interface it allows you to use the same operators and functions in a variety of situations: it’s nice that you can use the same operators and syntax to handle async behavior, empty values, potentially erroring operations, stateful operations, parsing, side-effects, etc. And I think the general idea that these concerns can be separated from the main logic of the code, is a good one.

                Yeah, but that’s the thing: I think that’s a false abstraction.

                It’s like OOP code where the developer insists on maintaining a strict public-private boundary, but ends up returning half the private attributes in public methods: you’re not actually abstracting your code better, you’re just hiding the implementation details behind a pointless façade with the exact same constraints.

                Maybe that’s my lack of experience in Haskell / mathematically pure functional programming. But I’ve never encountered a situation where monadic code was an organic abstraction that allowed any meaningful code reuse.

  9. Olivier FAURE says:

    I don’t think performance is the biggest problem of OOP. I think this is:

    The metaphor suggested by your class structure is a lie, and it keeps you from reasoning about what the program is doing. Your SpaceMarine isn’t actually carrying a Gun because in terms of data, there’s no such thing. Your “gun” is a combination of three completely different data structures:

    The shared gun model that gets rendered. There’s only one copy of this and it never changes.
    The collection of behavioral characteristics that define a particular type of firearm: Reload speed, fire rate, kick, accuracy, etc. These attributes might change from one type of gun to the next, but all SpaceMarines carrying the same type of gun will share a common copy of this data.
    A set of data describing what a particular SpaceMarine is doing with a particular gun. This includes what animation is playing, how many bullets are left, the attachment location at the SpaceMarine’s hand, etc.

    You’ve taken these three totally different concepts and mashed them together. Your code is promising that SpaceMarines carry guns like in the real world, but the reality is that SpaceMarines technically “share” a single gun. If another coder tries to modify the gun for a particular marine, they’ll be modifying the global gun used by everyone, so the code is actively misleading by pretending to work like the real world.

    This is my major gripe about OOP, and it’s something that I rarely see discussed: OOP makes for poor, deceiving abstractions.

    Object-oriented design will usually abstract away data (private attributes) behind behavior (public functions). This is fine for workflows where the behavior is fixed and the data can change, but it’s terrible in designs where the data stays the same, but behavior often changes.

    I’ve never been able to explain it clearly, but the problem is that OOP tends to really focus on doing things. An OOP interface will usually give you information about what the object conceptually does, but will usually not communicate information like “What data is this function allowed to change” or “Does this function have side-effects”. In other words, in a video game, there’s no way to know whether “SpaceMarine->Gun->Shoot()” just spawns a bullet, or spawns a bullet and plays a sound and creates a particle effect without looking at the code of the Shoot() method, as well as every single function method the Shoot method calls.

    (yes, technically you can have const parameters and the like; in practice the average game engine implementation will either have a “GameWorld” singleton, or a “GameWorld*” pointer inside the Gun class; like, in the diagram editor I work on in my day job, every single diagram object stores a “Diagram* m_parentDiagram” pointer)

    In a functional workflow, the Shoot() method will instead return a (Bullet, PlayedSound, ParticleEffect) tuple that the calling code will pass on outward and/or add to some sort of queue.

    This is useful if, say, the Shoot() method is used both in server and client code; for instance, the value of ParticleEffect can be discarded in the server version, which in OOP code would require adding a “if (isClient()) createParticle(…);” line, which is much less maintainable.

    The state of the art is using an ECS workflow to run functional code with very expressive abstractions (each system clearly states its scope through the list of components it operates on) and tight performance (components are stored and used sequentially, which is very good for the cache hit rate). The GDC talk on Overwatch’s architecture is a pretty good overview of the subject (thought it mostly focuses on the network applications).

  10. Gabriel says:

    Never loop a tug-of-war rope the way the guy all the way on the right is looping it! That’s a good way to lose your hand.

  11. pseudonym says:

    This means you’ll never have any surprise side-effects and things will theoretically be easier to debug. However, this style of programming is very tricky and has a large performance overhead.

    This might seem to be the case. But many languages that allow functional paradigms implement iterators.

    For example. There is a long text file with several data records. These records need to be parsed, a calculation has to be performed on each record, this needs to be transformed into a new file format record and this needs to be written to a file.

    That’s a lot of expensive transformations. First you read all the records into a big list, parse them in another big list, create a new list with calculations, transform that into another big list with new records, and then write that list to a file. You will need tons of memory to pull that off. This will be very slow, the CPU will spend most of its time idle, waiting for stuff to get from memory into the cache. The cpu is also waiting when the file is read, and also when the result is written. Very inefficient. I can understand that functional programming seems slow.

    Enter iterators. All those list transformation functions can easily be written into iterators. The file reader will read one record, this will be parsed by the next iterator, calculated on by the next iterator, transformed into a new record by another iterator and written to a file by the last iterator. Then the same thing happens for the next record. Until the whole file is read.

    This is extremely efficient. The filesystem is always busy reading and writing records, not waiting on the cpu to complete. The cpu always has something to work on. We only need as much memory as it takes to transform one record. This means all transformations will happen probably in the cache close to the cpu. Since CPU’s are extremely fast nowadays, and mostly waiting on data to be present in the cache this makes very good use of the hardware.

    We can read multi gigabyte files this way while using only a small percentage of system memory, and keeping the cpu fully occupied. Also if you write multiple programs this way you can use unix pipes to do the same thing across multiple programs. This is a very efficient way of big data processing that iterators allow.

    So functional programming with iterators is extremely performant. At least in the data science use case above. I don’t know how this can be translated to games though as I have no experience in that field.

    1. default_ex says:

      It applies just as well to game programming. Think of a graphics card like a car with a plugged intake. If you aren’t careful to structure your code so that it can do work on the CPU while having a healthy cache of work ready for the GPU. The GPU will detect this as not needing it’s full power and go into a lower power state. When you finally give it enough work to justify a higher power state, the pipeline stalls for state change and the player sees a micro-stutter. It’s also just bad for metal to let it cycle between hot and cold, keep it busy or let it rest.

  12. judgedeadd says:

    Data People want beautiful code and Object People want code that works.

    I think you have this sentence the wrong way around, it contradicts the previous paragraph.

    1. Shamus says:

      The point was to make it clear that Data People (also) want beautiful code and Object People (also) want code that works. I just want to make it clear that just because code beauty isn’t a top priority, that doesn’t mean they don’t care about it at all.

  13. Forty-Bot says:

    However, this style of programming is very tricky and has a large performance overhead

    The idea is to let the compiler note that “well the structure is the same except that x field is different” and then only modify x field instead of creating a new structure. GHC tends to be pretty good at this sort of thing.

Thanks for joining the discussion. Be nice, don't post angry, and enjoy yourself. This is supposed to be fun. Your email address will not be published. Required fields are marked*

You can enclose spoilers in <strike> tags like so:
<strike>Darth Vader is Luke's father!</strike>

You can make things italics like this:
Can you imagine having Darth Vader as your <i>father</i>?

You can make things bold like this:
I'm <b>very</b> glad Darth Vader isn't my father.

You can make links like this:
I'm reading about <a href="http://en.wikipedia.org/wiki/Darth_Vader">Darth Vader</a> on Wikipedia!

You can quote someone like this:
Darth Vader said <blockquote>Luke, I am your father.</blockquote>

Leave a Reply to John Cancel reply

Your email address will not be published.