Why be modular?

Published 2022/06/05

We want our software to be modular. We want it to be decoupled, well defined, and have interchangable behavior.

Right? Not exactly.

Let me explain why.

A Definition

Let's start by looking at an API. API stands for "application programming interface", which honestly, doesn't clarify much, so let's use some other definitions. I like to define an API as a set of functions and data meant to be used by other functions and data.

For example, say we have a set of functions and data meant to be used for drawing pictures. It might include things like:

def draw_rect(x1, y1, x2, y2):
"""Draws a rectangle with a top-left at (x1, y1) and a bottom-right at (x2, y2)."""
      
def draw_line():
"""Draws a line starting at (x1, y1) and ending at (x2, y2)."""
      
def get_size():
"""Returns the size of the bounding box of the drawing."""
    return [width, height]
      
# etc...
      
def display():
"""Shows the drawing."""
      
# etc...

Which other functions and data can then use.

I also like to define an API as a contract. It is a collection of promises that you make to the user of your API. Things that they can trust in.

These promises come in two forms:

  1. The first form of promise is the syntax of your API. You give users a way to talk to your API and you promise that that way will be consistent.
  2. The second form of promise is the behavior of your API. You associate some inputs with some outputs and side effects, and you promise that they will be consistent.

A Problem

But trying to keep our promises can get us into trouble.

We as humans want our programs to change over time. We want them to do new and more powerful things. Things that our promises... can get in the way of.

For example, say we want to add the ability to hide our drawing again after displaying it. We want to add a hide function, and change display to show to match. This means we will have to break a promise about the syntax of our API:

def show():
"""Shows the picture."""
      
def hide():
"""Hides the picture."""

As another example, say we want to add the ability to change the scale of our drawing (to essentially zoom it in or out). This means we will have to break a promise about the behavior of the get_size function of our API:

def get_size():
"""Returns the bounding box of the drawing."""
    return {width: width, height: height, scale: scale}

A Solution

So if the promises we're making are making it harder to add new things, the solution is simple: don't make those kinds of promises.

This is what being "modular" asks/helps you to do. It asks you to break your problem down, organize the functions and data in your API into understandable chunks, and really ponder the promises you're making.

More specifically, whether you're willing to keep them.

Perhaps if the designer of our original API had broken it down into understandable chunks (e.g. one module for creating the drawing, and one for displaying it) they would have made a different set of promises.

Conclusion

Being modular is not an end unto itself. And being modular does not, in itself, make a program good.

Modular programming is a solution to the problem of adding to a program over time, without breaking the promises you told others you would keep.