23

I recently read the Three Big Lies blog post and I am having a hard time justifying the second lie, which is quoted here:

(LIE #2) CODE SHOULD BE DESIGNED AROUND A MODEL OF THE WORLD

There is no value in code being some kind of model or map of an imaginary world. I don't know why this one is so compelling for some programmers, but it is extremely popular. If there's a rocket in the game, rest assured that there is a "Rocket" class (Assuming the code is C++) which contains data for exactly one rocket and does rockety stuff. With no regard at all for what data tranformation is really being done, or for the layout of the data. Or for that matter, without the basic understanding that where there's one thing, there's probably more than one.

Though there are a lot of performance penalties for this kind of design, the most significant one is that it doesn't scale. At all. One hundred rockets costs one hundred times as much as one rocket. And it's extremely likely it costs even more than that! Even to a non-programmer, that shouldn't make any sense. Economy of scale. If you have more of something, it should get cheaper, not more expensive. And the way to do that is to design the data properly and group things by similar transformations.

Here are my problems with this lie in particular.

  1. There is value in code being a model / map of an imaginary world as modeling the imaginary world helps (at least me, personally) visualize and organize the code.

  2. Having a "Rocket" class is, to me, a perfectly valid choice for a class. Perhaps "Rockets" could be broken down into types of Rockets like AGM-114 Hellfire, etc. which would contain payload strength, max velocity, max turning radius, targeting type and so forth, but still every rocket fired would need to have a position and a velocity.

  3. Of course having 100 Rockets costs more than 1 Rocket. If there are 100 Rockets on screen there must be 100 different computations to update their position. The second paragraph sounds like it is making the claim that if there are 100 Rockets, it should cost less than 100 computations to update the state?

My problem here is that the author presents a "flawed" programming model but doesn't present a way to "correct" it. Perhaps I'm tripping up on the analogy of the Rocket class, but I would really like to understand the reasoning behind this lie. What is the alternative?

closed as primarily opinion-based by gnat, 17 of 26, Caleb, BrianH, dagnelies Sep 8 '16 at 11:00

Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 9
    @gnat: This question is squarely within the province of software design, so I'm inclined to give it some leeway. – Robert Harvey Sep 7 '16 at 17:00
  • 12
    That blog post is pretty poorly written and doesn't defend and support its claims too well. I wouldn't give it much thought. – whatsisname Sep 7 '16 at 17:02
  • 21
    Whoever wrote that quote is an idiot with little understanding of OO concepts or how those are implemented in software. First, we are not mapping to an imaginary world, we are mapping to the real world. And if you have 100 rockets, only the the state of additional rockets uses additional resources, not the model or the behavior. He seems to have different ideas about it and suggests to fix a problem that does not exist. "Grouping similar things" as an optimisation may make sense sometimes but is totally independent of using classes or not. If you want to learn, steer clear of this charlatan. – Martin Maat Sep 7 '16 at 17:18
  • 3
    Considering that the author didn't bother to write more than 5 paragraphs total explaining the "3 Big Lies," you've probably spent more time thinking about the article than he did. If he won't bother making an effort, you shouldn't either. – Caleb Sep 7 '16 at 19:30
  • 9
    I think what he's getting at is, do you really need 100 [probably dynamically allocated with virtual methods too] "rocket objects", as opposed to a list of positions, a list of velocities, etc (having a list of all positions and a list of velocities means you might be able to use vector instructions to add the velocity to the position on each tick update rather than writing a naive loop through a list of objects) – Random832 Sep 7 '16 at 20:45
63

Firstly, let's look at some context: this is a game designer writing on a blog whose subject is eking out the last drop of performance from a Cell BE CPU. In other words: it is about console game programming, more specifically, console game programming for the PlayStation 3.

Now, game programmers are a curious bunch, console game programmers even more so, and the Cell BE is a rather strange CPU. (There's a reason Sony went with a more conventional design for the PlayStation 4!)

So, we have to look at those statements within this context.

There are also some simplifications in that blog post. In particular, this Lie #2 is poorly presented.

I would argue that everything that abstracts from the real world is a model in some sense. And since software is not real, but virtual, it is always an abstraction and thus always a model. But! A model doesn't have to have a clean 1:1 mapping onto the real world. That is, after all, what makes it a model in the first place.

So, in some sense, the author is clearly wrong: software is a model. Period. In some other sense, he is right: that model doesn't actually have to resemble the real world at all.

I will give an example that I already gave in some other answers over the years, the (in)famous Introduction to OO 101 Bank Account example. Here's what a Bank Account looks like in almost every OO class ever:

class Account {
  var balance: Decimal
  def transfer(amount: Decimal, target: Account) = {
    balance -= amount
    target.balance += amount
  }
}

So: the balance is data, and transfer is an operation.

But! Here's what a Bank Account looks like in almost every banking software ever:

class TransactionSlip {
  val transfer(amount: Decimal, from: Account, to: Account)
}

class Account {
  def balance = 
    TransactionLog.filter(t => t.to == this).map(_.amount).sum - 
    TransactionLog.filter(t => t.from == this).map(_.amount).sum
}

So, now transfer is data and balance is an operation (a left fold over the transaction log). (You'll also notice that TransactionSlip is immutable, balance is a pure function, the TransactionLog can be an append-only "almost" immutable datastructure … I'm sure many of you spotted the glaring concurrency bugs in the first implementation, which now magically go away.)

Note that both of these are models. Both of these are equally valid. Both of these are correct. Both of these model the same thing. And yet, they are exactly dual to each other: everything that is data in one model is an operation in the other model, and everything that is an operation in one model is data in the other model.

So, the question is not whether you model the "real world" in your code, but how you model it.

As it turns out, the second model is actually also how banking works in the real world. As I hinted at above, this second model is mostly immutable and pure, and immune to concurrency bugs, which is actually very important if you consider that there was a time not too long ago, where TransactionSlips were actual slips of paper that were sent around via horse & carriage.

However, the fact that this second model actually matches both how real world banking works and how real world banking software works, does not automatically make it somehow more "right". Because, actually, the first ("wrong") model fairly closely approximates how banking customers view their bank. To them, transfer is an operation (they have to fill out a form), and balance is a piece of data at the bottom of their account statement.

So, it may very well be true that in the core game engine code of a high-performance PS3 shooter, there will not be a Rocket type, but still, there will be some modeling of the world going on, even if the model looks weird to someone who is not an expert in the domain of console game physics engine programming.

  • 1
    Wouldn't this mean that good code models the real world and that in fact it is only a misunderstanding of the real world which causes a bad model and therefore bad code? – yitzih Sep 7 '16 at 20:55
  • 14
    I'd rather phrase it as "sometimes, the real world isn't what you think it is" or "what is 'the real world' depends on context". (Again, to a bank account owner, the data on the bottom of their statement is very real, while to a bank employee it is ephemeral.) I think the statement in the blog post is mostly caused by the author not understanding that "modeling the real world" does not mean "taking a photograph and making everything you see in there a class". – Jörg W Mittag Sep 7 '16 at 21:01
  • The front-end for your online banking application will probably treat balance as data and transactions as more data, and transfers as operations, because that is what the user sees, even though the back-end may treat it differently. – immibis Sep 7 '16 at 22:49
  • @yitzih: every model is an abstraction and simplification, so you could accuse every model of being incorrect, but that’s not constructive. Every model has to fulfill a purpose and must be good enough for that, not wasting resources for unneeded things. For a government’s software, a human might be someone who may participate in elections, has to pay taxes or can be married to another human, for our CRM software, a human is someone who is associated with orders and has a delivery address (and neither models how (s)he eats)… – Holger Sep 8 '16 at 9:16
  • 2
    If the human knows anything about banking they'll find the second easier, and since the banking techniques they know about were invented in order to make banking work, they can make banking software that works. Not because the second model is "more like the real world", but because it describes a better bank. The first model might be an equally-accurate representation of a real-world dysfunctional bank! Guess what: if you want good banking software then the programmers need to learn how to do banking well, if only from the requirements docs. – Steve Jessop Sep 8 '16 at 10:31
19

I disagree with every "lie" he proposes.

TL;DR The author of this article was trying to be controversial to make their article more interesting, but the so called "lies" are accepted by software developers for good reasons.

Lie #1 - Big O matters for scaling purposes. Nobody cares if a tiny application takes a longer time which is the only time constants matter, they care that when they double the input size it doesn't multiply the execution time by a factor of 10.

Lie #2 - Modeling programs after the real world allows a programmer looking at your code 3 years later to easily understand what it is doing. Code needs to be maintainable or you would need to spend hours just trying to understand what the program is trying to do. Another answer suggested that you can have more generic classes like LaunchPad and MassiveDeviceMover. These aren't necessarily bad class to have, but you would still need the Rocket class. How is anyone supposed to know what a MassiveDeviceMover does or what it moves? Is it moving mountains, spaceships, or planets? This basically means that adding in classes like MassiveDeviceMover makes your program less efficient (but possibly way more readable and understandable).

Additionally The cost of developer time began exceeding the cost of hardware a long time ago. It is a horrible idea to start off trying to design with optimization at the front of your thoughts. You program it in the easy and understandable way and then tweak your program after finding out which parts of your programs are taking a lot of time to run. Don't forget: 80% of the execution time is being used by 20% of the program.

Lie #3 - Code is extremely important. Well written (And modular) code allows for re-usablity (saving countless man hours). It also allows you to sift through and recognize bad data so it can be handled. Data is wonderful, but without the code it would be impossible to analyze and get useful information from it.

  • 5
    I think I'm more sympathetic to #3. In 30 years of programming, the vast majority of bugs, performance issues, and other problems I've seen were solved by fixing the data representation. If the data is right, the code practically writes itself. – Lee Daniel Crocker Sep 7 '16 at 20:49
  • 6
    The real issue with #3 is that it compares apples with oranges, not that code is more important than data or vice versa. – Doc Brown Sep 7 '16 at 20:52
  • 3
    Input data is out of your hands, but how to represent the data in your software is entirely within them. You might be calling that part of "coding", but I think it's not: for example, it is often independent of language and is often done before any coding begins. I agree, though, that code which cleans up ugly input data is often a good thing; but you can't do that until you have a definition of "clean". – Lee Daniel Crocker Sep 7 '16 at 21:05
  • 3
    I don't think Lie #3 is a Lie at all, actually. Fred Brooks already wrote decades ago: "Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowcharts; they’ll be obvious." (Nowadays, we would probably talk about "algorithms" and "data types" or "schemas" instead.) So, the importance of data has been well-known for a long time. – Jörg W Mittag Sep 7 '16 at 21:43
  • 1
    @djechlin My point wasnt that data isn't important or that code is more important. Simply that data isn't more important than code. They are both very important and rely on each other heavily. – yitzih Sep 8 '16 at 3:09
6

In an e-commerce system, you don't deal with "rockets" at a class level, you deal with "products." So it depends on what you're trying to accomplish and your desired level of abstraction.

In a game, it could be argued that rockets are merely one of many types of "moving objects." The same physics apply to them as to all other moving objects. So at the very least, "rocket" is going to inherit from some more general "moving object" base class.

In any event, the author of the passage you quoted seems to have overstated his case a bit. Rockets can still have unique characteristics, like "amount of fuel remaining" and "thrust," and you don't need a hundred classes to represent this for a hundred rockets, you only need one. Object creation is fairly low cost in most decent programming languages, so if you need to track rocket-like things, the notion that you shouldn't make Rocket objects because it might be too expensive doesn't make much sense.

5

The problem with the real world is all that damned physics. We separate things into physical objects in the real world because they're easier to move than individual atoms, or a giant molten slag of something that might potentially be a rocket.

Likewise, the real world provides a number of useful features that we rely upon. It's really easy to make Penguin exceptions - "all birds fly, except...". And it's really easy to label things as rockets, I mean if I call that penguin a rocket and light it... it just doesn't work.

So how we separate things in the real world conceptually works under those constraints. When we're doing things in code, we should separate things to work well under those constraints, which are decidedly different.

What is the alternative?

Think about networks. We don't model ports and wires and routers in code. Instead we abstract network communication into connections and protocols. We do that because it is a useful abstraction regardless of the implementation in the real world. And it puts useful constraints (eg: you can't communicate until the connection is opened) that only matter in code.

So yes, sometimes modeling code after the real world works, but that is a coincidence. When people talk about OOP, the objects are not real world objects. That schools and tutorials say otherwise is a decades long tragedy.

  • 1
    +1: Protocols are very much a "real world" abstraction. Even in today's world, the protocol officers are some of the most important staff people for a state visit, for example. Who goes first on the red carpet at the G8 meeting, Obama or Putin? Do they hug or shake hands? How do I greet an Arab vs. an Indian? And so on. We have plenty of "things" in the "real world" that aren't "things" in the "physical world". Modeling the real world doesn't mean modeling the physical world. Even if there isn't a Rocket type in this guy's code, I'm willing to bet that there is nonetheless some model of … – Jörg W Mittag Sep 7 '16 at 20:05
  • … the real world, even if it doesn't correspond to anything "physical" (in the sense of "touchable"). I wouldn't be too surprised to find actual "physical" objects (in the sense of "things a physicist might recognize") in there, though, such as quaternions, tensors, fields, etc. which are, of course, also "real-world things" and "models of the real world". – Jörg W Mittag Sep 7 '16 at 20:07
  • Alan Kay envisioned the Dynabook as a computer that would be given to children at birth and that would become an extension to their brain. The purpose of the MVC pattern then, would be to have the View and Controller bridge the gap between the brain and the Model to support the Direct Manipulation Metaphor, i.e. the illusion that the computer is just an extension of the brain, and that one can directly manipulate the Model Objects with one's thoughts. And that's what we mean when we say that the Domain Model models the "real world". It should implement the abstractions in our brains. – Jörg W Mittag Sep 7 '16 at 20:11
  • And when I think about a console game physics engine, then I probably do indeed not think about rockets, and thus there shouldn't be a model of a rocket in my code. But I'm probably thinking some other "real world thoughts", and there should be models of those in the code. – Jörg W Mittag Sep 7 '16 at 20:12
2

The alternative is to model the things that your programs care about. Even if your program deals with rockets, you may not need to have an entity called a Rocket. For instance, you might have a LaunchPad entity and a LaunchSchedule entity and a MassiveDeviceMover entity. The fact that all of this is in aid of launching rockets doesn't mean you are handling rockets themselves.

0

My problem here is that the author presents a "flawed" programming model but doesn't present a way to "correct" it. Perhaps I'm tripping up on the analogy of the Rocket class, but I would really like to understand the reasoning behind this lie. What is the alternative?

This is the real problem, but I will give you my take as a developer, maybe that will help.

First, I wouldn't call any of it lies, as common misconceptions. Calling it lies is just hype.

One He is right, in some ways. Not going to spend a lot of time on this, cause it's not part of the question. But in essence he is correct. I could restate this as "What works in a lab may not work in real life". Too many times developers stick with a design that works in a "lab" but fails in real world applications.

Three Sounds a bit soap boxy to me, but essentially he is correct again. But this could be rewritten to "write code around your needs, don't try to fit the needs to your code."

Two Again, here he is correct. I have seen developers spend weeks or longer developing a "rocket" class when a simple "vehicle" class would work, or an even simpler, "movable" class. If all your rocket needs to do is move from the left side of the screen to the right and make a sound, then you can use the same class you did for cars, trains, boats, and flies. The 100 should cost less then 1*100 argument seems to be in time spent developing, and not so much in computation costs. Though sticking to fewer general classes that an be reused is "cheaper" then many specific classes that can't be reused. This could probably be rewritten as "general classes are better then over specific classes, when the only reason for the more specific class is to meet a real world model of how things work."

In essence, the entire article could be rewritten, with fewer buzzwords and it would only be a paragraph long at best. That said, it's a blog post focused on a narrow area of programming. I have done some embedded programming, and I can agree, with the general idea behind these statements, though there is quite a bit of "fluff" around them to make it suitable for a presentation at GDC.

One last note, the article was written in 2008 (best I could tell). Things change quickly. The statements are true today, but embedded systems are much more common today then back then, and development patterns change. Perhaps even in response to this article/talk.

-1

I find it interesting that these lies center around academic concerns: The platform, efficiency of memory usage, and data. But it completely ignores the human element.

Software is about meeting people's needs. Typically this is quantified in business terms - there are customers who want something and backers who are willing to pay to make it happen. If software is being written in a way to meet the needs of both sides of the equation, then it is good software, if not, it is bad software.

If platform is not important to the customer, then platform is not important. If memory efficiency is not important to the customer, then it is not important. If data is not important to the customer, then data is not important. If the code works, but cannot be read or maintained, and the customer wants quick and reliable changes at a decent price, then poorly written code is a bad thing. If the code works, but cannot be read or maintained, and the customer doesn't care or is willing to pay for expensive refactors, then poorly written code is a good thing.

The big lie is that anything but the human element matters. Why is data important? Because there is some customer or stakeholder who needs it to be. That is the "big truth".

  • 4
    Unfortunately customers want quick and dirty code that is easy to read and maintain, cheap with no testing efforts, and no bugs. – Gonen I Sep 7 '16 at 17:32
  • @user889742 Ha! True. You have precisely stated the engineering problem architects have been attempting to solve for all time and what makes the industry such an interesting space to work in. – Price Jones Sep 7 '16 at 17:38
  • He ignores the human element because he's a game developer and the maintenance era of a game is relatively short lived, although longer today than it was in 2008. Day 1 patches seem to be the norm in gaming now a days. Back in 2008 patches for games were still relatively rare. – RubberDuck Sep 7 '16 at 23:34
-1

IMHO If code is "designed around a model of the world" it's easier to understand, both for the designers and developers , and for the maintainers. But I think it's not just me, and not just software. From Wikipedia: Scientific modelling is a scientific activity, the aim of which is to make a particular part or feature of the world easier to understand, define, quantify, visualize, or simulate by referencing it to existing and usually commonly accepted knowledge

Not the answer you're looking for? Browse other questions tagged or ask your own question.