Modular programming originated in the 1960s when developers began breaking up larger software programs into smaller parts. Despite being six decades old, this concept is still extremely relevant and remains useful for today’s software developer. It’s also one of the key programming principles followed and used at Tiny.
This post is the first in a series of articles on how and what programming principles we use at Tiny. The other articles in the series are:
Let’s start with some definitions.
What is modular programming?
Modular programming is a general programming concept where developers separate program functions into independent pieces. These pieces then act like building blocks, with each block containing all the necessary parts to execute one aspect of functionality.
When the blocks, or modules, are put together, they make up the executable program.
While most people talk about modularity at a file/folder/repository level, modularity works on multiple levels:
- Functions within files
- Files within repositories/libraries
- Libraries/repositories within projects
Modularity is about making building blocks, and building blocks made up of smaller building blocks. Each block is separate, strong, and testable, so you can stack them together at the end to create your application. Thinking about modularity in this way also means that you can build it into your code style and architecture.
For module types, you can create rules and guidelines around what modularity means at each level and how you handle certain things, like how each part is defined and located within files, folders, and libraries. It’s important to decide from the beginning what the constraints of each module type are, which makes for a better plan.
Two important module types to consider are:
- Program control modules
Designed to connect and coordinate another module, or modules working together.
- Specific task modules
Designed to complete one specific task, which is usually data transfer between modules.
Modules and APIs
One very useful way modules can interact is through interfaces or APIs. With an API, you only expose the bits of code the developers need to use the module, while hiding the rest.
In a way, this means your API can act like a contract, defining what the module/library does and how it can be used by outside code. For example, the API might say “this module has this function that does this and returns this”.
With APIs, you can be confident about what parts of the modules should or shouldn’t change without warning. That way, even when you need to change or fix things under the hood in a library, other things can keep using the API and trust that nothing unexpectedly changes. Plus, it’s much easier to glance at a module and know what it can be used for (more on this later).
Modular coding at Tiny
At Tiny, we’re very fond of modularity. For example, the TinyMCE monorepo contains several modules that each contain code relating to specific functionality. For example:
...and many more.
Each “module” contains files and folders that follow specific guidelines. Katamari is a good example – with its API and Util folders that contain specific kinds of code, as per our standards and guidelines:
- The API folder contains a file for each concept that is exposed via Katamari’s API
- All Katamari files are collected in the Main.ts file
This way, if we want to know what methods Katamari has for arrays, we can:
- Glance at the Main.ts to see what Katamari exposes
- Go to any of the API files – such as Arr.ts which contains methods for dealing with arrays
- Check on the methods within the file to see how the modules interact
As a result of this architecture, TinyMCE itself consists of a big dependency tree of libraries of helper functions (built on more libraries of helper functions!) that are all pulled together to make the editor. It might sound complex, but the benefits are very much worth the effort.
Modular programming examples
For some examples, pop over to GitHub to see that TinyMCE has specific modular programming types set, with files containing:
- Unicode helper functions
- Helper methods for dealing with Objects
- Helper methods for Arrays
… and the list goes on. Separating functions in this way can make it much quicker and easier to find what you’re looking for at a later date.
Advantages of modular programming
The key advantage of modular programming is that by breaking a large software program into smaller pieces, it’s easier to develop and maintain those same large projects. It comes with a number of benefits, eight of which are:
1. Code is easier to read
Modular programming usually makes your code easier to read because it separates it into functions – so that each only deals with one aspect of the overall functionality. As compared to monolithic code, it can also make your files a lot smaller and easier to understand.
For example, most of our TinyMCE files never get over a couple of hundred lines of code, whereas many other apps have thousands of lines of code in each file, which can make it hard to follow and find specific pieces.
However, modular programming can get messy if you split things into too many tiny functions, or if you’re passing data or functions around between too many files. But if you’ve split your modules up sensibly, it works much better than a function that’s hundreds of lines long!
In short – a small function with a good, descriptive name can help you understand a block of code without needing a comment.
2. Code is easier to test
Software that’s split into distinct modules is also ideal for testing. That’s because tests can be much stricter and more detailed when you test small functions that do less, as compared to big functions that do a number of things. This is especially true if you can only test the outputs of a function, not the steps it follows.
Plus, easier testing may mean fewer big, in-depth comments are needed, since tests can act as examples of how code works. If you don’t understand a block of code, checking out the tests can be one easy way to get a better understanding.
3. Easily find things later
Modularity involves grouping similar types of functions into their own files and libraries, and splitting out related helper functions into their own files (instead of leaving them mixed in with the core logic code).
With modular programming, you can make finding specific code even easier, by creating conventions for file names and locations. For example, the code for each community plugin in TinyMCE follows a specific folder structure that groups files relating to API, UI, and core functionality. If you can guess where a file might be and what the file or function might be called, it’s a lot easier to look for and find code.
4. Reusability without bloat
A lot of the time, you’ll need to use the same code or function in multiple places. Instead of copying and pasting the code, modularity allows you to pull it from a single source by calling it from whatever module or library it’s in.
For example, our Katamari and Sugar libraries are used in most of our projects, so it’s handy to be able to add them as dependencies, and handle data structures and DOM manipulation the same way in all our projects.
This reduces bloat and size because we don’t have multiple copies of each bit of code that performs a specific function.
5. Single source for faster fixes
With each module providing a single source of truth for your specific functions, it minimizes the number of places bugs can occur, and makes it faster to fix when bugs occur. This reduces the risk of problems caused by two pieces of code relying on slightly different implementations of the same functionality.
And if there’s a bug in the code or you need to update the specific function, you only have to fix it in one module, and everything that uses it gets updated right away. Whereas, if you copied and pasted the code into different places, you could easily miss updating one or two instances.
Note: Occasionally you’ll need to copy code for a specific reason. When this happens, we follow rules around the name and location of the code – to make it easy to find in the future.
6. Easier, lower risk updates
With modular programming, every library has a defined API layer, which protects things that use it, from changes inside the library. As long as you don’t change the API, there’s a much lower risk of unintentionally breaking code somewhere that relies on whatever you changed.
Of course, you still have to be careful, but APIs certainly help to make it clearer when you’re about to change a public function. For example, if you didn’t have explicit APIs and someone changed a function they thought was only used within that same library (but it was actually used elsewhere), they could accidentally break something.
7. Easier refactoring
With modular programming, refactoring can be easier. There’s a number of reasons for this, but as an example, both APIs and following a strict file/folder structure can help if you want to script or automate refactoring.
In late 2017, TinyMCE was converted to TypeScript. As part of the conversion, we also restructured TinyMCE to be more modular, by splitting more of the code out into plugins and libraries.
We were then able to leverage this increased modularity (particularly the API structure we introduced) when automating parts of the TypeScript conversion. It also helped with manual follow-up work, since we could work in sections, plugin by plugin and library by library. And years later it’s still helping us with onboarding new developers, finding and fixing bugs, adding new features, and many other things.
8. Easier to collaborate
Modular programming is essential where multiple teams need to work on different components of a program. When you have multiple developers working on the same piece of code, it usually results in Git conflicts and various other issues which can slow down the team.
Splitting the code between more functions, files, and/or repositories, means you can decrease the chances of conflicts happening.
You can also assign ownership to specific modules of code, ensuring teams are responsible for their part of the software, and enabling them to break the work down into smaller tasks. The teams (and modules) come together with clearly defined interfaces and public APIs, ensuring everything fits together at deployment.
What are the disadvantages of modularity in software design?
There must be a reason why some dev teams don’t use modular programming, right? Yes, there is.
Usually, the alternative to modular programming is to create a monolithic application of code, where all your code is (more or less) dumped into a single location. The downside of this method, is that it can result in “spaghetti code” as it’s twisted and tangled, like a big bowl of spaghetti 🍝
That said, there are three great reasons why some teams still write spaghetti code, instead of using modularity:
- Code size
Modularity can increase code size and impact performance if you can’t tree shake your dependencies
Sometimes complex file systems aren’t necessary and can add unnecessary overhead
Monolithic code can make it harder for people to mess with code that the original developer doesn’t want changed, hacked, or pirated
And to be honest, some developers just like to live on the edge. It’s kind of thrilling when you’re not sure what (in thousands of lines of code) is broken 😆
If you’re working on a personal or small project, where the size and complexity is small and there aren’t too many people collaborating on it, you might not benefit from taking a modular approach.
There are also plenty of older projects which may have become spaghetti over time, especially if people have tacked code onto the original project without fully understanding the best way to add it. These types of projects sometimes even reach the point where they need a structural overhaul, because they become too difficult to manage and carry too much tech debt.
So, even if your project starts out small, consider whether taking a modular approach from the beginning could help future-proof your code. Certainly, if you expect your project to have a long life or grow a lot, consider modularity from the start, because structural overhauls can be very difficult, time consuming, and hard to fit in when there are features to be written.
The value and future of modular programming
Modular programming isn’t new, but it’s still very popular, which is why it’s important for developers to learn what it is and why it’s useful. Why? Because the way that a number of today’s libraries, frameworks, and package managers are set up for code sharing and dependency management, makes modular programming the natural choice.
For example, there are many NPM libraries that only do one thing. And more and more developers are using frameworks that only do specific things, like Bulmer, which is a CSS framework that’s fairly simple compared to some of the other popular CSS frameworks.
Smaller libraries may not sound as impressive due to having less functionality, but there’s also less learning needed before you can use them, and they are usually smaller, so there’s less code bloat than larger libraries.
Use TinyMCE with a module bundler
Are you already implementing modular programming principles in your projects, or want to start playing around with the concept? Here’s a bonus for you.
You can use TinyMCE in your project using a module loader like Webpack or Browserify. Check the details on how to do it, in our documentation. We also have specific documentation on how to enable the PowerPaste plugin with a module loader.
Do you do modular programming? Why or why not?
Let’s continue the conversation on Twitter @joinTiny Let us know whether you or your team use modular programming and why. And please share what your favorite tool is that helps you make your code modular!