Une des exigences de l'ingénierie du logiciel est la production de composants assemblables et utilisables dans plusieurs réalisations, comme dans toute autre activité industrielle. La mise en uvre de cette exigence dans un langage de programmation est permise par la notion de module . Plusieurs langages, notamment Modula 2 et Ada, ont été conçus autour de cette notion. L'idée est qu'un programme est formé à partir de plusieurs modules ; les objets définis dans chaque module sont classifiés, soit publics , soit privés ; les objets publics d'un module sont déclarés dans son interface et sont exportables vers les autres modules, lesquels déclarent dans leurs interfaces les objets qu'ils importent . En particulier, cette distinction public/privé permet une discipline de noms : les noms privés, n'étant pas connus à l'extérieur de leur module, peuvent être réutilisés sans risque par d'autres modules.
C ne dispose pas d'une vraie notion de module, mais permet de réaliser la distinction public/privé de façon très simple et rudimentaire. C++ dispose également de mécanismes de modularité qui sont basés sur la notion de classe ; Java offre à la fois des classes et des packages . Le langage disposant actuellement de la meilleure notion de module semble être Standard ML.
La conception d'une bonne architecture modulaire est loin d'être évidente. La notion de structure de données permet de construire des modules de base d'une telle architecture. Une interface de structure de données constitue un type abstrait . Par exemple, le type abstrait des piles est formé des déclarations suivantes :
void mk_empty_stack(stack *const s);
int is_empty_stack(const stack *p);
void push(const stack_data x, stack *p);
stack_data top(const stack *p);
stack_data pop(stack *p);
Un module utilisant des piles doit connaître ce type abstrait, et peut ignorer la nature de l'implémentation des piles (par un tableau, une liste chaînée, etc). Il doit même ignorer cette implémentation, afin d'être indépendant de l'implémentation choisie, laquelle doit être modifiable sans remettre en cause les modules qui l'utilisent. Il ne faut donc pas qu'un module utilisant des piles comporte des expressions du style p->content[p->height - 1], qui n'ont de sens que pour une implémentation particulière des piles. Tous les langages ne permettent malheureusement pas de réaliser cette encapsulation d'une structure de données.
En C, l'unité de modularité est le fichier . Rappelons qu'un nom local n'est connu qu'à l'intérieur de la fonction où il est défini. Par contre, un nom global est connu dans un fichier à partir du point où une déclaration de ce nom est faite : un nom global est donc a priori public. Pour le rendre privé, c'est-à-dire pour limiter l'accès à un objet global au fichier où il est défini, on fait précéder sa définition par le mot-clé static :
static int t[10];
static double f(double t[]) { ... }
Un fichier comporte donc une suite de définitions d'objets ; celles qui sont désignées static sont privées, les autres sont publiques (donc exportables). Ce mécanisme impose une contrainte d'organisation : toutes les fonctions utilisant un même objet global privé (fonction, variable ou tableau) doivent figurer dans le même fichier. Par contre, les noms de types ne peuvent pas être privés, ce qui interdit une véritable encapsulation des données.