Modularity is dividing program into small components, or modules, each of such with closed functionality and with a stable architecture - working and complete, but perhaps, desirably, extensible still.
Modularity allows for software parts reuse and extending.
In my opinion best module is an implementation class(-es) with an abstract interface(-es).
In my opinion, modules should be:
1. Interface-dependent: modules should depend on a simple interface, a set of operations that can be used by other software parts.
In an Interface, we do not specify how we do things, but what we do.
In Implementation we precise how things are done. Different implementations of a given interface can do some things differently, for example: count slower, but with more precision, as long as it's not precised by interface requirements.
No unneccessary features & functions should be available to module's users, module should work like a black box which does not show every detail of it's functionality to user, only it's key functions, an interface.
This simplifies everyone's life, makes software cheaper, faster, safer, more robust.
Interfaces should be responsible only for one thing, even if it consists of many other smaller things.
Module users should depend on abstract interfaces, which allow for concretion's substitution.
We can choose between implementations as we like when composing software from parts, for example: we can choose & change later car's engine for more expensive but faster if needed.
For example: as we model Car, we say - abstractly - that it has an Engine, among other parts. Concrete Engine Model Choice depends on how we compose car, but design should allow us to choose as we prefer, or change as we need - thanks to relying upon abstract engine, not a concrete model or concrete family of models.
2. Composible: modules should be able to be composed from smaller modules, which represent things,
For example: car consists of many parts such as engine or wheels. both car, engine and wheel is a single thing,
3. Well-documented: modules & their interfaces should be easy to understand, thus easier to modify or repair,
4. Loosely-Coupled: a change or error in one module, should not cause other modules to be broken,
This can be achieved by managing dependencies between modules, avoiding dependency cycles, and isolating modules from each other, designing them so they can work independently, as well as in a collaboration,
Example of a dependency cycle: module A depends on module B, module B depends on module C, module C depends on module A,
Dependency is something that when changed causes a change or error in other part(-s) of software.
5. Open/Closed: Modules should be open for extension, but closed for modification.
We cannot change what module does, but we can either demand less to make it fulfill it's function or give more for the same, or lower price.
For example: we won't change a Car to Pillow, but we can either provide extra functionality such as air-conditioning capability, or remove some demands on client such as Processor, the Internet bandwidth, or Memory usage.
For example: we can attempt to optimize Car's functioning to use less of electricity, without changing it's functionality other way - as precised by functional requirements for an interface.
See also: SOLID.
Source: , my own experience.