But since I didn’t touch the other calls, code that wanted to use the highlightable push_button() directly to do things that are more complicated than a simple modifiable boolean could still be compact, too.
This is an extremely simple example of how important it is to maintain continuous granularity when compressing code. Before I started making Jon’s UI system support more compression, it was extremely granular. Everything was done by hand, so the user of the library had full control, but had to do a lot of work.
When I made Panel_Layout and started adding things to it, I was making a less granular way to use the UI system. The code that uses Panel_Layout makes less calls to it, and does less work, preferring instead to let Panel_Layout do some things automatically. But obviously anyone who needed full control could always just not use Panel_Layout and call the UI system directly. This created two levels of granularity, which was nice and flexible.
Adding bool_button(), instead of modifying push_button() was crucial in that it created a third level of granularity. If you happen to want a toggle boolean, you can call bool_button(). If you want a highlightable button, but don’t want a toggle boolean, you can call push_button(). And if you want something else altogether, you can still call the UI system directly.
If instead I had changed push_button(), I would have created a hole in the second level of granularity, a discontinuity. If you wanted a toggle boolean, you’d still be OK, because you call the modified push_button(). But if you want a highlightable button that doesn’t work with a boolean at all, you either have to go all the way down to the lowest level of granularity and call the UI system directly, or you have to introduce a temporary boolean everywhere in a very inconvenient way so that you can try to use the higher-level call.
Hopefully you can see how this extends to more complex situations. As subsystems and APIs become more complex, continuous granularity can become essential to avoiding very bad situations. In this simple example, the discontinuous case isn’t horrible. It’s not great, but it’s not that bad. In cases where there were more far-reaching effects of a granularity change, things can be much worse.
For example, suppose that I had been careless and ended up with a system where the granularity switch had cascading effects. Suppose that I’d made Panel_Layout’s data entirely private and didn’t support any way of querying anything in it. In that case, you would be forced to either use or not use Panel_Layout for everything in a panel. So if you’d done all your buttons with push_button() and the modified toggle-boolean push_button(), then got to a button that couldn’t use one of those, you’d literally be forced to rewrite the entire panel using the low-level UI calls! As horrible as that sounds, it is actually a very common thing to have happen in APIs these days, many of which are “designed” by people who do not actually know anything about API design.
It is always important to avoid granularity discontinuities, but fortunately, it is really easy to do if you just remember a simple rule of thumb: never supply a higher-level function that can’t be trivially replaced by a few lower-level functions that do the same thing. This is often a hard rule of thumb to follow if you do object-oriented programming, and is yet another reason that object-oriented programming is a bad idea. It’s something you have to explicitly design into the system of objects a priori, and it is often very difficult to ensure that you have done it correctly, since there are lots of details to get right and lots of circumstances you have to correctly predict (and there’s another problem here that deals with opaqueness, but I’m going to cover that in a later article).
If instead you are following a compression-oriented approach to programming, the rule of thumb is trivial to follow because it is how you made the code in the first place! It becomes an implicit result of the programming process, rather than an explicit issue you have to deal with. Since compression-oriented programming means building the code by progressively bundling pieces of code into functions that operate at higher and higher levels, so as long as you just don’t delete the smaller pieces as you build bigger ones, you automatically end up avoiding granularity discontinuities.
Last Week and Next Week
That brings me to the end of this week’s Witness Wednesday. Although I added a bunch more stuff to Panel_Layout (collapsible panels, tooltips, default controls for various things, scrolling, etc.), I can’t think of anything particularly special about any of them that warrants detailed coverage. So next week I will be moving on to “the main event” of this series, which is the construction of the new “Lister Panel”.
But before I sign off, I wanted to briefly address two types of comments that I received about last week’s Witness Wednesday.
First, there seemed to be some confusion about what I meant by “object-oriented programming”. Just to clarify, I don’t consider object-oriented programming to have anything to do with whether or not there may or may not be things that look like objects existing in the code base. To me, that would be a very silly way to use that word, because that would imply that, for example, you were doing “functional programming” simply because your program happens to have some functions in it that don’t have side effects.
Rather, when I say “object-oriented programming”, I mean literally that: you have oriented your programming around the objects. The objects are the important thing to you, and you are thinking in terms of the objects. It is the practice of thinking that objects are important (or that they’re even worth thinking about at all) that is wrong, and is best avoided. It is not objects themselves that are bad, because objects are really nothing more than some functions that happen to be strongly associated with some data. If you happen to end up with that in your programs, there’s nothing wrong with that. Sometimes some functions are very closely related to the data with which they operate, and that’s fine.
And to go one further, just to underscore that I’m talking about a way of programming rather than the result of that programming, if for some reason you really absolutely positively need to have a bunch of objects in your code in order for you to sleep soundly at night, as much as I might disagree with that opinion, I would still advocate that you build those objects using a compression-oriented approach! You would still be much better served to avoid pre-designing your objects, or your hierarchies, or whatever else it is that you want, and instead write the low-level code first and progressively compress it into a suitable set of objects.
Second, after last week’s article, multiple people strangely suggested that, for some reason, compression-oriented programming would only work with small numbers of people (perhaps one or two), but is somehow not possible to do or not effective when working on teams (or large teams), and so object-oriented programming must be used.
I don’t really understand this suggestion. I’d love to discuss it at some point, but honestly, I really just don’t even know what the underlying assumptions would be that would cause someone to arrive at this conclusion. I’m also not sure why they think that it isn’t something I’ve used effectively on projects that involved teams of people (which I actually have). So until I get a better idea of why someone would think that the number of people involved has anything to do with the efficacy of compression-oriented programming, I’ll just say this:
Writing code for yourself to use is no different than writing code for someone else to use. Unless you are some sort of idiot savant who remembers every piece of code they’ve ever written, it is always in your best interest to write reusable code that presents a nice, usable, hard-to-screw-up interface to the code that uses it, because even if it’s only you who’s using it, you’ll probably have to use it at some point when you’ve forgotten one or more of its internal details. Compression-oriented programming isn’t some kind of shortcut thing you do when it’s just you — it’s the right way to build code that presents a clean interface to a complex set of operations, which is basically what any non-trivial piece of code needs to do.
I’ve been on projects where I was the only programmer (RAD
’s Granny 3D
), I’ve been on projects where there were so many programmers that I didn’t even know who they were or how many there were (Intel
project), and I’ve been on projects where there were just enough programmers to make things interesting, like The Witness
, where there were five (well, seven, technically, but I don’t think all seven were ever on the project simultaneously). Throughout all of this, I have never once found myself thinking that the style of programming should change based on the number of people. I always adopt a compression-oriented approach, and if I am working on a team, as I get things to a point where I think they will be useful to others, I start putting things in a shared header file for them to use. That’s really all there is to it.
But, certainly, if there are a lot of specific issues that people are thinking of in relation to compression-oriented programming and object-oriented programming that vary based on the number of people involved, please write in and let me know what they are. If I get enough specifics I’ll do a whole article talking about them.