Модульность

С ростом вычислительных мощностей росла и сложность программ. Например, ядро операционной системы Linux написано на языке Си. Версия ядра 4.1 (2015 год) включает в себя 19,5 миллионов строк кода. Как можно догадаться, Линус Торвальдс пишет его не один, а ядро операционной системы заключено не в одном файле.

Разбиение программы на модули позволило существенно увеличить сложность программ и дала возможность работать над одной программой нескольким разработчикам.

Модуль — это законченный, автономный (в идеале) кусок программного кода, реализующий некоторую функциональность и предоставляющий интерфейс к ней.

Откомпилировав часть программы отдельно, можно значительно сократить время на компиляцию и сборку всей программы: если в проекте из 100 модулей было изменено только 2 (условно), то зачем компилировать остальные 98?

Драйвер — хороший пример модуля. Допустим, в некотором гипотетическом устройстве используется датчик температуры DS18B20 и дисплей с контроллером ILI9341, предназначенный для отображения температуры. Программист А берется за реализацию драйвера температурного датчика, предоставляя две функции в качестве интерфейса: инициализации и запроса температуры. Программист Б реализует драйвер управления дисплеем, предоставляя в качестве интерфейса функцию инициализации контроллера и функцию вывода текста на экран. Всё, что остается программисту В — взять эти файлы, подключить их к своему проекту и дописать основную программу, которая будет работать с этими устройствами.

В языке Си модуль обычно состоит из двух частей — файла с исходным кодом и заголовочного файла. Для датчика потребуются файлы: ds18b20.c, ds18b20.h. Аналогично и для контроллера дисплея. Если модуль достаточно сложный и большой по объему, имеет смысл добавить файл с окончанием _conf для его настройки, где могут храниться макросы с указанием используемых портов ввода-вывода и другой используемой периферии. Для удобства портирования всю аппаратно-зависимую часть можно вынести в файлы _port.c / _port.h. Если модули были написаны правильно, то вся необходимая информация для программиста будет содержаться в заголовочных файлах (интерфейс модуля), а детали реализации будут скрыты в файлах исходного кода.

Негласное правило

В целях предотвращения коллизий в именах, все публичные идентификаторы (прототипы функций, типы, переменные и т.д.) должны начинаться с имени модуля. Например, функцию инициализации дисплея следует назвать ili9341_init(), а не просто init() и вероятно не lcd_init(), так как в другом проекте вы можете использовать другой драйвер.

Чтобы получше разобраться в модульности, стоит посмотреть, как выглядит процесс компиляции. Некоторые сущности будут даны без пояснений и раскрыты позже по тексту.


Изменено: