I’m pretty sure most of the non-beginner people in production already know about this, but I wanted to write about this because I’ve had a moving experience with modules recently.
It’s occurred to me that it’s hard to really get the importance of modularization on the first try. I’ve recently witnessed a class learning object-orientated programming in C++ coming from C, and while some people get modularization with encapsulation, others didn’t get it on their first try. I’m of the stance that modularization is probably one of the most crucial lessons for “getting” programming, and it’s also the one that naturalizes order against entropy.
I mean, just being able to perform namespacing is a form of modularization and separation if used conventionally, and just namespacing a pretty crucial feature once we start a fiesta with more than maybe 10 libraries/modules/packages/gems/etc. (see: R packages; aliasing on import and dplyr/plyr).
Really, a lot of things come from the concept of modularization and DRY working together: “Modules? Libraries? Packages? Static vs. dynamic linking? Interfaces? Functions? Objects? Who cares?” the coder might think, annoyed by seemingly large projects with pointless cruft when everything can be put into main().
But those are core concepts! But getting somebody to intimately understand the “why” besides just saying it for show might take a little bit more.
It’s really hard to explain to somebody why it’s important until you actually encounter it. It’s like something you sort of need to experience, either as an immersive mind experiment to understand the frustration, or you need to go down the wrong path to experiencing it. Maybe I’ll write about it sometime.
Either way: here’s a good visual for modularization and 3 reasons why it’s good thing:
1. It reduces mind load.
Separation of concerns is one of the best things about modularization of code. It is also what enables procedural and functional programming. Being able to reuse parts of code in places where it can once again repeat its purpose without having to manually retrace your steps to program it (again) is great!
Additionally, some people say that the human mind can focus on about four to seven distinct things at once. If that were so, then the mind should only be able to consider a limited amount of distinct “steps” in a procedure until it can consolidate several steps into an “umbrella” step, like how thousands of lines of code can result in a single compression function or something like “allocate this piece of memory for me.”
The human brain doesn’t seem capable of simultaneously focusing on every single line of code at once. Modularization reduces the mind load on the programmer by hiding underlying details and wrapping it up into one nice “capsule” of concept or usage, leaving more “mind-space” for more concepts in thinking.
2. It makes for easier debugging.
If you have a program to bake a cake, and main() contains everything from ordering the supplies to delivery, when the entire thing crashes, where in the code do you check first?
“Well, it’s in main(),” one might say. “But, main() is 25,000 lines long…”
Some people might turn to step-wise debugging with something like GDB. Others might turn to test cases. Embedded programmers might have to deal with print-like debugging with LED switches. A few might move to static analysis tools (squee!!!).
Ultimately, your debugging is going to have to take you back to where the error is.
Modularization reduces the space (and, so, time) in which you need to look for bugs. Being able to find the part of the program in which there might be errors by knowing other modules have already been tested or proven to be bug-free under normal operating conditions is extremely valuable! Not having to look for bugs in, say, standard library functions is a huge productivity boost. You want to be squashing bugs in your program’s code, not the code that holds it up, whether you wrote it or not.
This sort of “possibility space” reduction is also crucial for certain algorithms as well, such as the binary tree search. Being able to place things into different “buckets” of concept or steps is always helpful.
3. It enables abstraction.
Being able to “block out unnecessary details” is a big part of many interfaces of all kinds. For a given interface, hiding implementation details is often seen as a security and usability boost, but, again, it also simplifies the way somebody uses it and thinks about using it.
Power, simplicity, and consistency might be a few words to describe what certain good interfaces have in common. Simplicity in coherence and consistency are what modularization provides. What better starting point for abstraction?
Modularization enables abstraction by being the mechanism by which to tie details together to create a tight abstraction. Trying to create a “facade API” that breaks if people don’t play by the rules isn’t going to work out well for people if they decide to try and break your system. Having the ability to turn your abstract concepts and rules into an encapsulated system is a very powerful tool in your toolset as a programmer.
So what does that mean when I code?
I guess we could turn it into a list like this:
- Smaller is better. It’s also the heuristic for…
- Simple is better. Simple things usually take less to code.
- One procedure, one purpose. Unix philosophy was made with this in mind, right, and we know how successful that’s been.
- Close your modules and systems. A closed system is such that its guts won’t fall out if you shake it up. Code that doesn’t break inside its own assumptions is crucial for stability. Not doing this in, say, C, will often result in something like a segmentation fault. (Because WHY would THIS pointer be NULL?)
- Diagram and flowchart between “modules.” It’s not easy drawing a flowchart with only one bubble. If your modularized code corresponds to bubbles, then as you split up your code, the relationships between your states and transitions make more sense as you begin to more closely map bubbles to individual steps in a more complex process.
- Leave the door open by leaving things small. One of the things about modularizing and abstraction is that your designs can easily break. When a client comes to you and asks, “Hey, can you add this feature?” and you didn’t think about allowing for that in your abstraction system, suddenly, you’re stuck with an entire system that can’t bend over without breaking.
- In this situation, maybe you’re left with a few choices: create a strong initial, flexible design; solidify and cement your design according to specifications or requirements after the point they can change; make the system easy to extend with a non-closed system (carefully guarded); or, maybe don’t take your system too seriously, and get ready to scrap and rebuild.
- Perhaps the best idea would be to build from primitives small enough that changes can be done like how we might mix and match puzzle pieces–just take some out and shove other ones in. It’s much easier to work with small puzzle pieces then trying to small a large block of code, replace the certain parts that need to be changed, and hopefully put everything back together.