A blog about in-depth analysis and understanding of programming, with a focus on C++.

Thursday, September 04, 2008

Modular Programming and the Push Model

Thus far, we've talked often about the low-level details of code. We've basically been operating on the function level and what goes inside them. This article will thus be the first to deal with the entire structure of code.

I practice what I (and probably others) call "Modular" programming. That is, when working on a project of any real size, the project is broken down into a set of discrete modules (built as C++ static libraries). Each module represents code that does a single, specific thing. That is the most important aspect of module design: modules should be as focused and minimal as reasonable.

Note that it is "as reasonable," not "as possible." And there is a good reason for this. The more modules you have, the harder it is to find a problem. Bugs can be local to a module, or they can arise through interactions between modules. The more modules you have, the more interactions you have to debug through. So there is tension between having specific modules and ease of debugging.

Keeping modules minimal is key to making this work. One of the primary benefits of modular programming is code reuse: if you code is broken down into discrete, atomic bits, it is much easier to use later. The bigger and more complicated the module, the less likely that it will be a good fit for your later needs.

One big problem with modules is interdependency. There will be modules that depend on the presence of certain other code. In many cases, this is an intrinsic need, but sometimes it is not. The real problem come with telling when a module truly needs another module.

The reason this is a problem is that the more dependent modules are on one another, the less likely you are to reuse that module.

Let's say you're writing a music player application, ala WinAMP or Foobar. Breaking it up into modules, you need at least the following modules:

  • Music playing. Takes a file and plays music, with various controls to stop, get the current time, etc.

  • Tag identification. Takes a file and determines the various tag bits in the file.

  • Playlist manager. Has a list of files in an order, can re-order them, has one that is "current", etc.

  • This is hardly comprehensive, but it will do for now. The question is this: does the playlist manager need the tag identifier module? Yes, but only if the playlist manager module includes the code for drawing the playlist. If it instead just a wrapper around a std::vector (or more likely, a std::list) of filenames, no. Some other code will be responsible for drawing the list and processing user input/output: a module who will be dependent on both the playlist manager and the tag identifier. The proper modulaization of this is to make the playlist low-level, and provide a higher level module that understands the GUI module and so forth.

    If this sounds similar to the old UNIX philosophy of "do one thing, do it well," then you're right. However, this is applied to code rather than executables, since code can more easily communicate with other code than executables with other executables.

    The fundamental key to all this is what I call the "Push Model". The Push Model describes how modules interface with other modules, as well as how modules internally work. If module B calls any function from module A, then module B is dependent on module A. If module A is dependent on module C, then B is also dependent on C, but indirectly.

    Also, the Push Model says that the results of a function call should be based only on the function parameters, the state of the class being called, some module-global state, and the module-global state of any module that is directly or indirectly dependent on this module. There are exceptions, of course (resources, input systems, errors, etc), but that's the general rule.

    The Push Model ensures that a module is truly modular.

    However, this is all nice in theory. There are always problems. We'll get to that in another article, though.