Качественная архитектура ПО на примере концепции Linux «всё есть файл»
Как концепция «всё — файл» помогает писать качественный код? Рассказываем об архитектуре ПО на примере набора библиотек FFmpeg.
8К открытий8К показов
В статье показано, как в Linux/FFmpeg организована кодовая база на C с учётом расширяемости, которая работает так, будто в C есть полиморфизм. Вы увидите, как концепция Linux «всё — файл» работает на уровне исходного кода, а также как FFmpeg позволяет быстро и легко добавлять поддержку новых форматов и кодеков.
Качественный дизайн ПО — введение
В процессе работы над кодом программисты регулярно сталкиваются с тем, что качественный дизайн кода окупается впоследствии при усложнении продукта. Для создания полезного и легко поддерживаемого в долгосрочной перспективе ПО разработчики подбирают определённые шаблоны и объединяют их в абстракции, и похоже, что разработчики Linux и FFmpeg поступили именно так.
При разработке программ создаются структуры данных и определяются их зависимости и поведение. То, как они построены и связаны между собой, можно рассматривать как дизайн/архитектуру ПО.
Предположим, что мы разрабатываем фреймворк для обработки видео- и аудиофайлов. Кодеки AV1, H264, HEVC и AAC производят некоторые идентичные операции с данными, и если мы разработаем некоторую обобщённую абстракцию, включающую эти операции, мы сможем использовать эту абстракцию вместо того, чтобы реализовывать конкретную идею, заложенную в каждом отдельном кодеке.
Ещё один хороший приём — использовать слабо связанные компоненты, чётко определив их функции.
Ruby
Возможно, эти концепции проще понять на практике. Сделаем примерный набросок фреймворка для обработки потоковых медиаданных, использующего несколько разных кодеков.
Этот код на Ruby отражает одну из описанных выше концепций. Без конкретизации в коде предполагается, что каждый кодек реализует функции encode и decode. Поскольку Ruby — язык с динамической типизацией, любой класс может иметь реализацию этих двух операций и работать как кодек.
Такой дизайн кода можно назвать хорошим, поскольку если нам потребуется добавить новый кодек, нужно только включить его реализацию в список. Разумеется, список можно сделать и динамическим. Смысл примера в том, что такой код легко расширять и поддерживать, поскольку компоненты слабо связаны между собой и каждый из них делает только то, что должен.
Фреймворк Ruby on Rails подталкивает к определённым способам организации кода, реализуя архитектуру «Модель-Представление-Контроллер» (MVC).
Go
Обращаясь к языкам со статической типизацией, таким как Go, нам придётся быть более формальными, описывая требуемые типы, но мы всё равно можем создать код, аналогичный приведённому выше.
Тип interface в Go намного мощнее аналогичной конструкции в Java, так как его определение никак не связано с реализацией, и наоборот. Можно даже присвоить каждому кодеку тип ReadWriter и использовать в таком виде.
С
На C тоже можно создать код с аналогичным поведением, но будут некоторые отличия.
Примечание Код создан по примеру размещённого на сайте Computer Science from the Bottom Up.
Сначала в обобщённой структуре мы определяем абстрактные операции (в данном случае функции). Затем мы наполняем их конкретным кодом, например кодером и декодером кодека av1.
Множество других языков поддерживают сходные механизмы распределения методов или функций, как если бы они придерживались некой конвенции. В результате ПО на уровне ОС достаточно уметь работать только с показанными высокоуровневыми абстракциями.
Linux kernel и концепция «всё — файл»
Концепция «всё — файл» ОС Linux позволяет использовать один интерфейс для работы с любыми ресурсами системы. Например, Linux обрабатывает сетевые сокеты, особые файлы (такие как /proc/cpuinfo) и даже USB-устройства как файлы.
Этот подход облегчает разработку программ для ОС, поскольку мы можем использовать хорошо изученный набор операций для абстракции, названной «файлом». Вот как это работает:
Это возможно только потому, что концепция файла (структуры данных и операции) была разработана как один из главных способов взаимодействия подсистем. Вот участок API-структуры file_operations
:
Эта структура чётко определяет то, что мы подразумеваем под концепцией файла, и какое поведение мы от него ожидаем:
Здесь можно увидеть набор функций, реализующих это поведение, в файловой системе ext4.
Даже файлы cpuinfo proc реализованы через эту абстракцию. Фактически, работая с файлами под Linux, вы используете виртуальную файловую систему (VFS), которая в свою очередь обращается к функциям абстракции.
FFmpeg — форматы
Вот общая схема архитектуры процессов FFmpeg, демонстрирующая, что внутренние компоненты связаны в основном через такие абстрактные концепции, как AVCodec, а не напрямую через конкретные кодеки.
Для входящих файлов в FFmpeg создаётся структура AVInputFormat, реализуемая любым форматом (видеоконтейнером), который требуется использовать. Файлы MKV также заполняют эту структуру своей реализацией, как и формат MP4 — своей.
Такой дизайн позволяет легко интегрировать новые кодеки, форматы и протоколы. В мае 2019 года в FFmpeg был включён кодек DAV1d (аналог av1 с открытым исходным кодом), и, изучив изменения в коде, вы увидите, насколько безболезненно прошло внедрение. В итоге ему только требуется зарегистрироваться в качестве доступного кодека и придерживаться списка общих операций.
Безотносительно используемого нами языка мы всегда можем как минимум попытаться создать код со слабой зависимостью и высокой согласованностью. Именно эти два основных свойства позволят вам писать ПО, которое легко расширять и поддерживать.
8К открытий8К показов