Пакеты, системы, модули, библиотеки — КАКОГО?
По моим наблюдениям, минимум раз в неделю в списке c.l.l или другом Lisp-списке «новички» путаются в том, что связано с пакетами. Говорят о «загрузке» пакета, «требовании» (requiring) пакета, удивляются тому, что после загрузки системы нужно пользоваться маркерами пакетов и т.д. Меня это раздражает, думаю также, что это может быть одной из причин, почему начинающие считают, что использование библиотек в Lisp сложнее, чем есть на самом деле.
Обычно я прекращаю попытки написать полезное объяснение, и, естественно, это объяснение очень простое. Я создал эту страницу, чтобы в следующий раз просто отправить сюда, вместо того, чтобы снова и снова объяснять одно и то же.
Прежде всего следует иметь ясную голову. Термин «пакет» сильно перегружен. В дистрибутивах Linux вроде Debian или Gentoo есть «пакеты», «пакеты» есть в языках программирования Java, Perl или Python. Вполне вероятно, что вы пришли в Lisp с предвзятым мнением относительно того, что такое «пакет» или чем он должен быть.
Пакеты
Пакетом в Common Lisp называется полноправный элемент языка, семантика которого четко определена стандартом. Более того, из всех обсуждаемых на этой странице терминов, этот — единственный, имеющий (в контексте Common Lisp) однозначное определение. Пакеты — это, строго говоря, контейнеры для символов. Можно сказать, что они нужны для помощи в организации отдельных пространств имен в ваших программах.
В Common Lisp есть функции и макросы для создания, изменения, исследования и удаления пакетов. Очень хорошее введение в пакеты (и символы) можно найти в главе 21 великолепной книги Practical Common Lisp Питера Сайбела. Определение термина находится в главе 11 (онлайн-версии) стандарта ANSI Common Lisp specification.
В общем, про пакеты это всё. Говоря технически, вы не загружаете пакеты. Вы можете загрузить (с помощь LOAD ) код, который в свою очередь создаст пакет, и это существенное различие.
Кроме того, если ваш Lisp жалуется, что не может найти какой-то пакет, это означает, что пакета как Lisp-объекта нет в образе (т.е. FIND-PACKAGE возвращает NIL ), потому что его еще никто не создал. Это не означает, что Lisp-машина поискала в файловой системе и ничего не нашла. (Частая причина такой неудачи состоит в том, что события происходят в неправильном порядке. Об этом ниже.)
Системы
Системы, в отличие от пакетов, даже не упоминаются в стандарте. Тем не менее, опытные Lisp-программисты знают этот термин, поскольку им потребуютется знать и применять какой-то инструмент определения систем. Наиболее заметный сегодня — ASDF (используется большинством Lisp-библиотек с открытым исходным кодом); другой известный инструмент определения систем, гораздо старше ASDF — MK:DEFSYSTEM. Некоторые разработчики также поставляют свои инструменты определения систем вместе с дистрибутивами, см. например, Common Defsystem для LispWorks.
В этом ключе система, строго говоря, это набор кода плюс инструкция по его обработке, например, зависимости от других систем, что следует загрузить/скомпилировать в первую очередь и т.д. Другими словами, инструмент определения систем по своему назначению похож на make или Ant.
Кроме того, инструмент определения систем обычно может намного больше — Common Defsystem может, например, интегрировать файлы библиотек типов COM, ASDF полностью расширяем и использовался, среди прочего, для компиляции файлов на C. Он также часто используется для определения тестовых наборов описываемой системы.
Хотя ASDF и весьма популярен, он не вездесущ. Он идет предустановленным со многими Lisp-системами вроде SBCL, OpenMCL или AllegroCL, вероятнее всего, что он загрузится и в других Lisp-системах, но этот факт не делает его частью Common Lisp. Это набор кода без явной спецификации и с разными версиями, которые бывают несовместимы между собой.
Поди пойми…
Модули
Например, в LispWorks можно использовать
для загрузки парсера, способного читать определения на C, но это не сработает на OpenMCL. Также можно вызвать
для загрузки ASDF на OpenMCL, но не в LispWorks.
Библиотеки
Скорее всего вы не найдете четкого определения, что такое библиотека. Большинство людей думают об этом как о коллекции кода, предназначенного для выполнения одной или нескольких определенных задач и распространяемого как единое целое, обычно в виде сжатого архива, который можно откуда-то скачать. На самом деле, это неясное определение является, думаю, наиболее подходящим при разговоре о программах, написанных на Lisp. Большинство Lisp-библиотек сегодня включают в себя определение (ASDF) системы, но это вовсе не обязательно. Возможно, в зависимости от способа получения, это будет модуль в вашей Lisp-системе, но и это тоже не обязательно. Кроме того, библиотека обычно определяет один или несколько пакетов, а может и не определять ни одного.
И, по соглашению, а может из-за недостатка фантазии, может сложится и часто складывается ситуация, когда библиотека «Ку» идет с определением системы «Ку», которую можно загрузить как модуль «Ку». После загрузки кода получите новый пакет, называемый «Ку». Четыре разных сущности с одинаковым именем! Я допускаю, что это сбивает с толку, но надеюсь, что несколько предыдущих абзацев помогли слегка прояснить ситуацию.
Но у меня все еще ничего не работает!
Часто люди жалуются, что они не могут скомпилировать файл, содержащий код вроде этого:
Почему так? Почему я могу загрузить этот файл, но не могу скомпилировать его? И почему я могу скомпилировать его после загрузки? Не странно ли?
Нет, не странно. Компилятор читает первую форму (которая является инструкцией скомпилировать — если необходимо — и загрузить систему CL-PPCRE, но не выполнить ее. В конце концов, компилятор заинтересован лишь в компиляции кода. После выполнения первой формы он переходит ко второй форме, к определению функции. Здесь возможно сообщение об ошибке, так как Lisp-сканер, пытающийся читать эту форму, обнаружит последовательность символов «cl-ppcre:scan», которая должна обозначать внешний символ из пакета CL-PPCRE, но самого пакета CL-PPCRE еще нет. В процессе загрузки системы CL-PPCRE, кроме всего прочего, создается пакет CL-PPCRE, но этого еще не произошло. Читайте главу 3 CLHS.
Можно воспользоваться EVAL-WHEN для указания компилятору загрузить CL-PPCRE перед чтением второй формы. Следует, однако, найти другой способ организации своего кода. Первая форма — это просто обявление того, что ваш код зависит от системы CL-PPCRE. Такое не должно находиться в том же файле, что и Lisp-код. Напишите определение системы для вашей программы и поместите зависимости туда.
Что такое пакет в программировании
Пакеты прикладных программ (ППП) служат программным инструментарием решения функциональных задач и являются самым многочисленным классом программных продуктов. В данный класс входят программные продукты, выполняющие обработку информации различных предметных областей.
Установка программных продуктов на компьютер выполняется квалифицированными пользователями, а непосредственную их эксплуатацию осуществляют, как правило, конечные пользователи – потребители информации, во многих случаях деятельность которых весьма далека от компьютерной области. Данный класс программных продуктов может быть весьма специфичным для отдельных предметных областей.
Пакет прикладных программ (application program package) – комплекс взаимосвязанных программ для решения задач определенного класса конкретной предметной области.
Инструментарий технологии программирования обеспечивает процесс разработки программ и включает специализированные программные продукты, которые являются инструментальными средствами разработчика. Программные продукты данного класса поддерживают все технологические этапы процесса проектирования, программирования (кодирования), отладки и тестирования создаваемых программ. Пользователями технологии программирования являются системные и прикладные программисты.
Характеристика пакетов прикладных программ. Классифицируются:
Основные тенденции в области развития ПОС:
Особенности: высокие требования к технической части обработки систем, наличие библиотеки встроенных функций и объектов, интерфейсов и баз данных.
Пакеты общего назначения
Элементы CASE-технологии в процессе корректировки содержат:
Серверы БД – успешно развивающийся вид программного обеспечения:
DAL – Data Access Language – для создания запросов на выборку данными из сети;
SQL – для распределения запросов.
Самая большая проблема серверов баз данных – обеспечение целостности базы данных.
Генераторы отчетов (серверы отчетов):
Сервер отчетов включает:
Подготовленные отчеты рассматриваются клиентами по электронной почет или другим транспортным агентом. Серверы отчетов обычно поддерживают разнородные платформы, поэтому эффективно работать в других вычислительных сетях.
При описании текстовых процессоров следует выделить две группы программных продуктов этого типа. Первая группа ориентирована на создание документов разной степени сложности с мощными средствами форматирования и включения графики. Типичным представителем этой группы является WinWord. Вторая группа текстовых процессоров (их часто называют текстовыми редакторами) ориентирована для работы с чисто текстовыми файлами, среди которых могут быть тексты программ, написанные на различных языках, конфигурационные файлы, файлы настройки и др. Ярким представителем таких программных продуктов является MultiEdit версий, начиная с 5.0. Этот текстовый процессор имеет мощную систему контекстной замены, встроенный язык макрокоманд на уровне Visual Basic, средства поддержки внутренней среды, средства помощи при наборе ключевых слов.
Возможности: автоматическое форматирование документов, вставка рисунков объектов и графики, составление оглавления и указателей, проверка орфографии, шрифтовое оформление, подготовка шаблонов документов.
Развитие данного направления программных продуктов является издательские системы.
Табличный процессор – удобная среда для вычислений конечного пользователя. Средства деловой графики, специальной обработки встроенных функций, работа с базами данных, статистическая обработка данных и программ.
Средства презентационной графики
Набор нескольких программных продуктов, функционально дополняемых друг друга, поддерживающих единые информационные технологии, реализуемые на общей вычислительной и операционной платформе.
Яркий представитель – Microsoft Office. Компоненты пакета могут работать изолированно друг от друга. Основное достоинство – их разумное сочетание друг с другом.
Характеристика интегрированного пакета:
— DDE и OLE (динамическая компоновка объектами);
Методоориентированные пакеты прикладных программ
Современный табличный процессор Excell:
Офисные пакеты прикладных программ (ППП):
Браузеры, средства создания интернет-страниц и прочего;
Электронная почта – важный компонент;
— Различаются платформами на которых работают, ценой, условиями распространения, поддерживаемыми транспортными протоколами, интерфейсами и сетями;
— Должна обеспечивать шифрование информации, факсимилию подписи, проверка орфографии на любом языке, управление сообщениями.
Настольные издательские системы
Программные средства мультимедиа
Основное назначение этого класса программных продуктов:
Создание и использование аудио- и видео информации для расширения информационного пространства пользователя.
Программные продукты мультимедиа заняли лидирующее положение на рынке в сфере библиотечного информационного обслуживания, в процессе обучения, организации досуга, БД компьютерных изображений, произведений искусства, библиотечных звуковых записей – все это основа для прикладных обучающих систем, компьютерных игр, библиотечных каталогов и фондов.
Системы Искусственно Интеллекта (ИИ)
Данный класс ПП реализует отдельные функции интеллекта человека.
Основными компонентами системы ИИ являются:
Разработка интеллектуальных систем ведется по следующим направлениям:
Интеллектуальный интерфейс включает:
Модуляция/демодуляция голоса – главный фактор в отставании проектирования интеллектуального интерфейса.
Семантическая сеть – модель знаний, в форме графа, в основе таких моделей лежит идея о том, что любой из знания можно представить в виде совокупности объектов и связи между ними.
Классификация семантических сетей:
1. Однородные (с единым типом взаимодействий);
2. Неоднородные (с различными типами взаимодействий).
По типам отношений делятся:
Теория программирования: пакетные принципы и метрики
Чтобы применять любые принципы правильно, сначала нужно их понять — то есть осознать, откуда они взялись и для чего нужны. Если применять вслепую всё, что угодно — результат будет хуже, чем если бы мы вообще не использовали эти принципы. Я начну издалека и сначала расскажу про абстракцию.
Что есть абстракция?
Это обобщение существенного и удаление несущественного, так как мир настолько сложен, что запрограммировать удаётся только его существенные части. Если попытаемся запрограммировать всё — мы потонем, поэтому абстракция помогает нашему мозгу «впихнуть невпихуемое», как это умеют делают военные (а программисты — пока нет):
Наши проблемы идут от мозга и от того, как устроена наша память. В отличие от хорошего хранилища долговременной памяти, кратковременная устроена по типу stack:
Еще мы используем Chunking (группировку) всякий раз, когда важно запомнить что-то большое. Например, чтобы запомнить число 88003334434, мы разделим его на группы по типу телефонного номера: 8-800-333-44-34. Для нашего мозга получится 5 объектов, которые запомнить легче, чем пытаться удержать число целиком или отдельно каждую его часть.
В коде мы используем слои, чтобы облегчить эту задачу. Однако пять таких слоев в голове удержать становится сложно, а больше пяти — уже проблема. Для лучшего представления это можно сравнить с неправильной супер-лазаньей, в которой слишком много слоёв и — они все слиплись. И это гораздо хуже, чем спагетти — потому что спагетти мы можем отрефакторить и что-то нормальное в результате получить. А чтобы получить что-то нормальное из такой лазаньи, её надо сначала растерзать и превратить в понятные и очевидные спагетти, а потом заново собирать лазанью, только уже правильную.
Поэтому, чтобы совладать с неподъёмно сложными системами, изобретают архитектуру, используя абстракцию. Это не цель, а инструмент, и одновременно — необходимое зло, так как все эти классы, супер-паттерны и прочие шутки не от нашей хорошей жизни.
Но как построить абстракцию, не сделав хуже?
Существует два понятия: cohesion (связность) и coupling (связанность). Они относятся в первую очередь к классам, но в целом и ко всем остальным сущностям. Разницу мало кто видит, так как звучат они почти одинаково.
И, хотя оба означают связь, coupling понимают в негативном ключе. Один объект завязан на другой в плохом смысле, если, ломая один из объектов, ломается всё остальное по цепочке. Cohesion несёт в себе позитивную ноту. Это группировка, в которой то, что близко по смыслу — лежит в одном месте и взаимодействует примерно с теми же местами, опять же близкими по смыслу.
Для того, чтобы понять, coupling у вас или cohesion, существуют проверочные правила. Их сформулировал инженер и специалист в области информатики Роберт Мартин еще в 2000 году, и это — принципы SOLID:
Что есть пакет?
Пакет — это группа единиц кода. Причем, пакеты – это не обязательно пакеты Maven или Composer, или npm. Это программные модули, — то, что вы выделяете в namespaces или иным способом группируете.
Обычно имеются в виду классы, но это могут быть и библиотеки, и модули (не те, которые в фреймфворках, а которые изначально описывали в объектно-ориентированном программировании, то есть группы относящихся друг к другу классов в отдельном namespace). И даже микросервисы можно назвать пакетами — если это настоящие микросервисы, а не те макро-, на которые частенько распиливают монолиты.
И того, кто эти пакеты пилит, обычно волнуют два вопроса: Как правильно формировать пакеты и как работать с зависимостями пакетов?
Как их правильно формировать?
Собственно, cohesion и coupling, как основополагающие принципы, отлично работают и для пакетов. Но сработает ли SOLID для пакетов?
Да, но не совсем. Оказалось, что существуют ещё 6 принципов от того же Роберта Мартина, сформулированные в том же году. Часть из них относится к cohesion, и это о том, как дизайнить код: REP, CCP, CRP. Другая часть — это coupling (то есть использование пакетов): ADP, SDP, SAP — и это о том, как сделать так, чтобы один пакет не завязался на другой и чтобы всё нормально работало:
1 принцип – REP (Reuse-Release Equivalency Principle)
На сегодняшний день этот принцип выглядит до смешного очевидным, но не забываем, что сформулирован он в 2000 году, когда не было таких замечательных штук, как Maven, Composer и прочих, а пакетные релизы были не частыми.
Принцип гласит: «The granule of reuse is the granule of release. Only components that are released through a tracking system can effectively be reused. This granule is the package. — что переиспользуем, то и релизим. Эффективно переиспользовать можно только компоненты, релизнутые через системы версионирования. Такие компоненты называются пакетами». То есть упаковывайте то, что переиспользуется в отдельные пакеты и релизьте это через любимый пакетный менеджер, версионируя по SemVer.
2 принцип – CCP (Common Closure Principle)
«Classes that change together are packaged together — изменение в пакете должно затрагивать весь пакет». Этот принцип очень похож на SOLID-ский OCP. Классы, которые изменяются по одной и той же причине, должны упаковываться в один пакет. Что логично.
Нормальный пример: адаптеры. Библиотека, допустим, кеш. Если мы запихиваем в один пакет тучу адаптеров: для файлов, memcached, Redis, то при попытке изменить один какой-то адаптер мы нарушаем два принципа. Во-первых, принцип REP (начинаем релизить один из адаптеров, а релизить приходится все). А во-вторых — принцип CCP. Это когда классы для адаптера под Redis изменяются, а все остальные адаптеры в пакете —нет.
3 принцип – CRP (Common Reuse Principle)
«Classes that are used together are packaged together — Пакеты должны быть сфокусированными. Использоваться должно всё». То есть классы, которые используются вместе — упаковываем вместе. Проверочное правило здесь такое: смотрим, используется ли в нашем софте всё из того пакета, который к нему подключен. Если используется чуть-чуть, значит, скорее всего, пакет спроектирован неверно.
Эти три принципа дают понимание, как пакеты дизайнить. И казалось бы, нормально делай — нормально будет. Однако реальность сурова, и я сейчас объясню — почему. Вспомним треугольник от Артемия Лебедева, который вершины «быстро», «дёшево» и «качественно» обозначил несколько другими словами. Такой же треугольник нарисовали и для пакетных принципов в Институте Макса Планка:
Получается, эти принципы конфликтуют, и в зависимости от того, какие стороны треугольника мы выбираем, вылезают соответствующие косяки:
Теперь переходим к принципам использования.
4 принцип – ADP (Acyclic Dependencies Principle)
«The dependency graph of packages must have no cycles — Если есть циклические зависимости, то проблема вызывает лавину». Если есть циклы, то есть зависимость пакета зависит от самого пакета прямо или косвенно, то косяк в одном пакете вызывает лавину во всех остальных пакетах, и ломается абсолютно всё. К тому же, такие пакеты очень тяжело релизить.
Поэтому надо проверять, есть ли циклы. Для этого необходимо строить направленный граф зависимостей и смотреть на него. Руками это делать не очень удобно, поэтому для PHP есть библиотека clue/graph-composer, которой скармливаешь пакет, и она строит гигантский граф со всеми зависимостями. Смотреть на это, конечно, невозможно, поэтому надо зайти в PR#45, зачекаутить его и выбрать возможность исключать зависимости, которые не интересны. Допустим, если вы пишите фреймворк, то вам скорее всего интересны зависимости на свои пакеты, а чужие — не так сильно, ведь свои косяки поправить можем, чужие — тяжелее. И получается вот такой граф:
Если мы видим — как здесь — что циклических зависимостей нет, то всё отлично. Если есть, надо исправлять. Чем меньше зависимостей, тем проще.
Как разорвать цикл?
5 принцип – SDP (Stable Dependencies Principle)
Это принцип стабильных зависимостей: «Depend in the direction of stability — Не получится строить стабильное на нестабильном». Нестабильность считается так:
Если на нас завязалось очень много всего — скорее всего, мы стабильны. Если же мы завязались на много всего, то, очевидно, мы не очень стабильны. Как повысить стабильность? Следующим принципом.
6 принцип – SAP (Stable Abstraction Principle)
Принцип стабильных абстракций гласит «A package abstractness should increase with stability — Стабильные пакеты абстрактны / Гибкие конкретны». То есть абстрактность должна возрастать со стабильностью. Стабильность здесь — то, как часто нам приходится менять части пакета: классы, интерфейсы, или что-либо ещё. Абстрактные пакеты должны быть стабильны, чтобы безболезненно на них завязываться. В примере с тем же кэшем пакет с интерфейсом будем сверхстабильным, потому что менять интерфейс, про который мы договорились и хорошо над ним подумали — скорее всего, не придётся. Если мы, конечно, абстрагируем не СУБД.
А вот гибкие пакеты, наоборот, конкретны — они должны быть нестабильны, чтобы мы их легко меняли. Все наши приложения, наши конечные пакеты — это на самом деле дико нестабильная штуковина, которая зависит от тучи всего и ломается чаще, чем пакеты, от которых она зависит.
Можно ли измерить абстрактность?
Конечно. Абстрактность — это число абстрактных классов и интерфейсов в пакете, деленное на общее число классов и интерфейсов в этом самом пакете:

Еще есть такой полезный показатель, как D-метрика, в которой по вертикали — нестабильность, а по горизонтали — абстрактность. По двум зонам — вверху справа и внизу слева — мы можем понять:
Линия посередине называется главной линией, и если классы и интерфейсы попадают на неё или выстраиваются вдоль — это тот случай, когда всё отлично. По сути, D-метрика — это дистанция от главной линии, поэтому 0 в этом случае — это хорошо, а 1 — плохо. Но, как правило, ни то, ни другое не случается — значения плавают в диапазоне от 0 до 0,9-0,7. Считается метрика так:

Для PHP есть 2 инструмента для того, чтобы посмотреть метрику своих пакетов:
Как и SOLID, все эти дополнительные принципы и метрики — не догма, но могут быть весьма полезными.
Резюме
Конечно, есть области, в которых можно сознательно игнорировать все эти принципы, отдавая себе отчёт в том, что за этим стоит и где вы можете получить проблемы.
Данные принципы же позволяют не скатываться в монолит или в left-pad из npm. С left-pad была в свое время история — его создали для добавления символа в конце строки, так как в JavaScript есть традиция дробить пакеты вообще в пыль. А потом на этот пакет завязалось практически всё — вплоть до пакетных менеджеров и самых крутых фреймворков. В какой-то момент автор обиделся на всех и выпилил left-pad из системы — после чего, как вы понимаете, сломалось всё. Рассмотренные принципы, в том числе, позволяют уменьшить вероятность такого сценария.
Единственная конференция по PHP в России PHP Russia 2021 пройдет в Москве 28 июня. Первые доклады уже приняты в программу!
Купить билеты можно тут.
Хотите получить материалы с предыдущей конференции? Подписывайтесь на нашу рассылку.






