Defining a Single Enumerant
By Casey Muratori
In the previous two articles I showed some examples of how coding can be made more efficient and intuitive by focusing on incremental compression rather than up-front design. However, implicit in that process is the assumption that the programmer actually knows all the options they have available for compressing things at each point in the development process. For inexperienced programmers, that’s often not the case, because they simply haven’t seen that many different techniques for structuring code, so they may not know the most efficient way to compress something down.
In these next few articles, I’m going to address that part of the process directly. I have selected a piece of code that I wrote for The Witness which does a lot of operations in a relatively small amount of code, and I’m going to go through it step by step, describing in detail why I chose to structure the code in the way that I did.
The Lister Panel
I rewrote a UI element in The Witness editor called the “Lister Panel”. Originally, it was just a list of “layers” in the Witness editor that could be hidden or shown by clicking on them in a list. Technically it also had the ability to list all the objects in the game of a certain type, but nobody used that anymore because it didn’t even have scroll bar support, and the list of objects in the game of any type you might pick had long ago surpassed the number that could fit on the screen at once. My primary purpose in rewriting the panel was to add scrolling support, but while I was at it I wanted to upgrade the panel to be much more powerful.
In particular, I wanted it to be able to do basic queries, like “show me all the grass entities that are hidden” or “show me all doors and cables that are not locked”. But I was only going to spend two or three days total working on it, so I needed to make sure that I kept everything nice and simple so it did not turn into a “project”, as it were.
I knew that the new panel had to do at least as much as the old panel did. Since the old panel could act as either a layer lister or a list of entities of the type selected by the “entity panel” (another UI element in The Witness editor), that meant the new lister panel would have to be somewhat modal as well. So I began the rewrite with a simple enum, so I could do different things depending on the mode of the panel:
enum Lister_Type_Mode
{
LISTER_TYPE_MODE_LIST, // normal listing of entities
LISTER_TYPE_MODE_ENTITY_PANEL, // listing of entities sync'd to the entity panel
 
LISTER_TYPE_MODE_COUNT,
};
That’s about as simple a piece of code as it gets, but already there’s a ton of experience baked in here.
First, the most obvious: I always put a “count” enumerant at the end of every set of enumerants. Why? Because that way you can trivially iterate over all of them later by just doing:
for(int Mode = 0;
Mode < LISTER_TYPE_MODE_COUNT;
++Mode)
{
// ... do something for each mode here ...
}
Since enums are considered constant by the compiler, you can also easily define arrays with the same size as the number of enums:
int OneIntPerMode[LISTER_TYPE_MODE_COUNT];
Since unspecified enumerants in C++ are required to always go in order, starting from 0, and increasing by 1 each time, this means as long as I always put a count enum at the end of the enumerants, no matter what I do to the enumerants on the inside, I’ll always have an easy and accurate way to know how many there are. Of course, this only works for simple sequential enumerants  —  if you start defining the values of enumerants, all bets are off. But unfortunately, there’s no way in C++ to say that an enum is required to only be sequential, so you just have to know what you’re doing.
I also used the starts-from-zero property of enumerants here, even though it doesn’t look like it. I am a big fan of initializing variables to prevent the bugs that crop up when you don’t. I feel like it’s always a win, because if you find that initializing variables is too slow later, you can always trim during optimization. But up until very recently, there was no way to specify how things were initialized in C++ at the point of declaration. You always had to manually write a constructor that initialized variables in a struct or class, and you could easily forget to initialize something in the constructor when you add it to the struct definition.
So I basically never use constructors for this purpose. Instead, I try very hard to ensure that all my code always uses 0 as the proper initial value for everything. That way, I can just do:
some_type_of_mine Var = {};
and I will be assured that it is initialized properly, regardless of how many new fields I add to it. I never have to worry that I may have forgotten to add an initializer to its constructor. Furthermore, any dynamic memory usage involving a struct knows it can initialize it with a simple memset-to-zero of the entire size, and that makes it really easy to write code that works with arbitrary types without having to use templates or other constructs that push the type information around so that the appropriate constructor can be called.
So, with this enum, I have implicitly used the starts-from-zero property to make LISTER_TYPE_MODE_LIST the default. Its value is zero, so when the lister panel is initialized, the mode field will be cleared to zero, which is LISTER_TYPE_MODE_LIST, the default mode.
Those are the two straightforward things I did with the Lister_Type_Mode enum, but there is a third, more subtle thing to note here as well: I didn’t create a virtual base class with derived classes for each mode.
Segregation, Obfuscation, Proliferation
If you’re the type of person who really likes object hierarchies, instead of starting with an enum for a mode specifier, you might have started typing something more like this:
class Lister_Type_Mode
{
virtual void update();
// ...
};
 
class Lister_Type_Mode_List : public Lister_Type_Mode
{
virtual void update();
// ...
};
 
class Lister_Type_Mode_Entity_Panel : public Lister_Type_Mode
{
virtual void update();
// ...
};
So you’re probably wondering why I didn’t do the same. There are a lot of reasons, but they all fold into one primary one: it’s a lot more typing for a lot less flexibility. As a simple example, how would you switch between modes?
In the simple enum way, there’s just something like
Lister_Type_Mode mode; // cleared to zero on init
// ...
mode = LISTER_TYPE_MODE_ENTITY_PANEL;
and you’re done. It obeys zero-is-default, so I can use clear-to-zero to initialize the Lister Panel. It is a simple piece of data, so you can copy it, pass it around, do whatever you want with it, write it out as a block, serialize it. It’s all trivial.
By comparison, if you went the hierarchy route, changing the mode looks more like this:
Lister_Type_Mode *mode; // cleared to zero on init
// ...
if(mode) delete mode;
mode = new Lister_Type_Mode_Entity_Panel;
Well, OK, that’s not too ugly yet. But how would I serialize this, for example, or pass a mode in from something that wanted to specify which mode to use? I’d have to add another virtual function to all my derived class that returns or writes out a particular ID from which they could be reconstructed, perhaps. And then I’d also have to make a “factory” function somewhere that can create the right derived class by taking that ID and doing the appropriate new. And if I don’t want to use RTTI, because that’s not necessarily well defined across compilers and platforms, I would have to define that ID myself, and it would probably look something like… wait for it… this:
enum Lister_Type_Mode_ID
{
LISTER_TYPE_MODE_LIST, // normal listing of entities
LISTER_TYPE_MODE_ENTITY_PANEL, // listing of entities sync'd to the entity panel
 
LISTER_TYPE_MODE_COUNT,
};
Look familiar?
Yes, the sad truth is that most of the time, if you think you are going to replace something simple like an enum that’s used to control a mode with a class hierarchy where you have one class per mode, you often end up implementing the enum scheme inside it anyway. So it’s not only more typing to implement, but you still have to do all the typing from the enum method as well!
Now, I would like to point out that, although it sounds like it, this is actually not me dissing object-oriented programming again (I’ll do that a bit later). This diss is more specifically targeted at C++, because there are plenty of object-oriented languages out there where it would not be necessary to implement the enum yourself  —  it could be created from the hierarchy itself automatically in a stable and useful way that is not janky like C++’s feeble RTTI. And as C++ continues to bloat to Stay-Puft Marshmallow Man proportions with every passing spec, it will probably be the case eventually (if it is not already) that you can use a bunch of new language features to perhaps eventually get to the point where it is not so onerous to use the class-based method even in C++. But honestly, even at that point, it would still be a lot more typing than just the simple enum list.
And we haven’t even gotten to the biggest problem yet. The biggest problem is that hierarchies severely limit your options for code compression. The point of an object hierarchy, and traditional object-oriented design in general, is to segregate code. It is about taking things that look like this:
switch(mode)
{
case LISTER_MODE_TYPE_LIST:
{
// do the stuff for list mode
} break;
 
case LISTER_MODE_TYPE_ENTITY_PANEL:
{
// do the stuff for entity panel mode
} break;
}
and turning it into this:
// In Lister_Mode_Type_List.cpp
void Lister_Mode_Type_List::do()
{
// do the stuff for list mode
}
 
// ...
 
// In Lister_Mode_Type_Entity_Panel.cpp
void Lister_Mode_Type_Entity_Panel::do()
{
// do the stuff for entity panel mode
}
This type of code transformation does only two things: it creates a hell of a lot of typing (you need to match declarations in the .h files now, one for every type, plus one in the base class), and it segregates the code. Segregating code is heralded as a good thing by OOP-advocates, but they rarely (never?) actually look at the practical costs of doing so. Those practical costs are actually quite high. As a simple example, suppose I wanted to do this:
int shared_x = 32 + get_base_x();
switch(mode)
{
case LISTER_MODE_TYPE_LIST:
{
set_my_x(shared_x);
} break;
 
case LISTER_MODE_TYPE_ENTITY_PANEL:
{
set_my_x(shared_x+5);
} break;
}
In the enum scheme, that shared_x is trivial, costs one line of typing to share, and is extremely easy to read and understand. Anyone coming to this code for the first time sees exactly what’s happening. But what happens if we do it “the OOP way”?
// In Lister_Mode_Type.cpp
void Lister_Mode_Type::get_shared_x()
{
return (32 + get_base_x());
}
 
// ...
 
// In Lister_Mode_Type_List.cpp
void Lister_Mode_Type_List::do()
{
set_my_x(get_shared_x());
}
 
// ...
 
// In Lister_Mode_Type_Entity_Panel.cpp
void Lister_Mode_Type_Entity_Panel::do()
{
set_my_x(get_shared_x()+5);
}
What was previously a single, easy-to-read function that anyone could understand has now been fractured into a proliferating set of functions that likely require jumping around through multiple source files to understand. You can’t even fall back to the debugger to watch what happens conveniently, because only one type of object is being worked with at a time, so you would have to trace through two separate mode behaviors and then mentally fuse them together to understand how they were related. This may not seem like such a big deal in this trivial example, but try to imagine how this extends out to a fully implemented, complex system and hopefully you will agree that it creates a substantial burden to legibility.
This kind of code segregation often goes by positive-sounding names, depending on the particulars (code vs. data, etc.), such as “abstraction”, “encapsulation”, “division of responsibility”, etc. But what it really should be called is “obfuscation”, because that’s the one thing that it definitely always does. It makes it very hard to see what something actually is doing. It makes the behavior of the program hard to understand in exchange for the supposed benefit of making each “object” segregated from the rest.
It might also be more accurately called “proliferation”, since you end up writing a lot more code to do the same thing. Instead of common pre and post code just going before and after a switch statement, you’re now talking about having to make entire shared functions which, if you’re lucky, can be structured such that there is (yet another) function in the base class that dispatches them automatically, or if you’re unlucky, have to be remembered every time a new class is implemented because they need to be called inside the derived class’s operation function.
Organization Criteria
At the end of the day, it’s always worth noting that the CPU executes some instructions. That’s what your program does. An “object-oriented program” is no different than any other program in the end, especially if we aren’t talking about the performance of the program.
So doing things like segregating operations into multiple functions that belong to separate classes doesn’t really have anything to do with the actual act of programming the CPU. Performance aside, it is strictly about how you choose to organize your code. The typical OOP way to organize your code is to consider the “objects” in the system as primal, and to segregate code into files that correspond to the operation of each of them. This implicitly optimizes programmer access to cross-procedural information as it relates to the object, so that they can see everything that is unique to a particular object quickly and easily. Conversely, it makes it substantially more difficult for the programmer to access procedural information quickly, forcing them to look across potentially dozens of source files just to piece together what a single function might do, as it dispatches to objects in any number of ways, each of which being potentially virtualized.
Similarly, modifications to a single object are made more optimal in OOP. If I want to change the behavior of a specific object, everything is usually in one place in the source code and I can make that change easily. However, changes to the behavior of a function that uses those objects may be extremely costly, potentially requiring a change to every object involved if, say, new information needs to be propagated to the objects in order for them to do what they need to do.
For at least a certain period of time, and to some degree still today, the object-oriented way of organizing a program was very popular. So it’s definitely true that there is at least some mental appeal to this form of organization. The problem with it comes when you actually look at what sorts of code modifications you tend to make most often, the cost of OOP organization (at least in C++) is much too expensive for any conceptual benefit you may feel it confers.
Most of the time, how an object is implemented internally is usually not the thing that changes most frequently, yet this is what OOP organization optimizes for (in file locality only  —  it does not actually change the amount of code that has to be modified). Instead, usually the thing that changes most frequently is how an object is used and what information it needs to have in order to do its job, and these are things that the OOP organization is uniquely bad at.
Just to be more concrete, consider something as simple as changing the number of parameters that an operation might need. For example, the non-OOP function:
void do(thing *a)
{
switch(a->type)
{
// ... cases for 5 different objects here ...
}
}
If that needs to have an additional parameter, one line has to change. You just add “, int b” to the end of the do() parameter list and you’re done. Anyone in the switch can use it, or not, at their discretion. If instead we do it the object-oriented way, now we have two choices. Either we change six separate do() function prototypes (the base class and the five derived classes) and their six separate definitions in twelve different files to take “, int b” at the end, or we introduce a new virtual function that “thunks” to the old function, introducing cognitive and computational overhead to avoid having to change code in lots of places.
Essentially, what you get when you structure things in the virtual dispatch way is an O(n) instead of O(1) cost for changing functions. That might be interesting if you got something back, but you really don’t. It’s not as if you have reduced an O(n) cost elsewhere. If you want to introduce a new type to the system, for example, in the non-OOP case you have to add a new case to each switch statement you cared about. In the OOP case, you’d have to add a new function for each virtual function you cared about. Both are O(n), although the OOP case is still a bit worse, being O(2n) since you have to add both the header and the implementation.
I could go on for quite some time about all the ways in which OOP creates more work in these scenarios, but hopefully you can extrapolate from here.
Now, I know a lot of people are probably thinking something like, “yes, OOP costs more to maintain, but the benefit is that people can’t misuse the objects” or something like this. It is a very common thing that I hear, especially in reference to “large teams”, about OOP helping to reduce the number of mistakes that programmers make when using other people’s code. I find this to be a rather nebulous and specious argument.
The reason is because I’m not really sure what kind of mistakes people are talking about. They’re rarely specific about these “mistakes” that are being “prevented” by OOP. I strongly suspect that what they’re actually talking about doesn’t have anything to do with the transformation of things from O(1) switch functions to O(n) virtual functions, which is expensive, but rather just the fact that hiding an implementation can be beneficial if there is a clear API boundary.
That is to say, if I believe there is a point in the code where there is no benefit to be gained by mixing it with the code that uses it, I can draw a hard line there and have a strictly opaque boundary between the two. I certainly wouldn’t knock this programming construct  —  I use it all the time. I’ll make a header file with some functions in it and a forward-delcared struct that houses the data, and then I’ll define the struct and the functions in the actual code file so that nobody who includes it will actually touch them. It’s a nice technique, and it certainly falls under the category of “encapsulation”.
But it is important not to apply this technique to everything, which is what OOP advocates. Much of the time, you actually want code to be miscible. Most parts of your program really do depend on each other, and it makes the code extremely difficult to manage if you try to artificially separate it into “objects” where there are no natural lines of separation. This is specifically the case with the “mode” concept I illustrated in the previous section.
The key thing to understand is that it is not worth trying to “prevent mistakes” if you are also preventing good, maintainable code! Preventing mistakes should only be a focus if you know you can do so without producing bad code as a result. Otherwise you might say that you went ahead and already made a coding mistake in the name of preventing some potential future coding mistake.
Three Thousand Words on Seven Lines
Yes, that was about three thousands words about nothing other than defining a single enum with only two actual values in it. When I said I was going to go over the implementation of this code in detail, I was not exaggerating.
Putting things in perspective, the fact that I can write so much about such a small piece of code is a good example of why it’s hard to learn how to program well, and also why good programmers don’t always write good programs. There’s so many things to think about at every turn, sometimes you just can’t afford the extra mental focus and push forward without considering everything and managing the tradeoffs as well as you should. I know this certainly happens to me all the time, and I’ve written many a piece of crappy code that, if I were to look at it in a calmer circumstance, I would find disgusting.
Furthermore, it is almost certain that my lengthy discussion of this simple enum is far from complete. I bet there are a bunch of experienced programmers reading this right now who have a number of points they’d like to add to the discussion. And that’s not even counting the object-oriented programming aficionados who are livid right now at my dismissal of their preferred methods, and would certainly have a lot to say about that.
So really, everything here just underscores the point with which I began the first article: good programmers need to talk more about how they actually code and what they think about when writing code. To that end, I’ll be back next week to continue examining the Lister Panel code, and I hope other folks out there will take some time to write about how they structure their programs and what they find to be efficient ways to write code, too!
For more information on my current projects, join the Molly Rocket Mailing List: