Русский
Русский
English
Статистика
Реклама

Теория программирования пакетные принципы и метрики



Чтобы применять любые принципы правильно, сначала нужно их понять то есть осознать, откуда они взялись и для чего нужны. Если применять вслепую всё, что угодно результат будет хуже, чем если бы мы вообще не использовали эти принципы. Я начну издалека и сначала расскажу про абстракцию.


Что есть абстракция?


Это обобщение существенного и удаление несущественного, так как мир настолько сложен, что запрограммировать удаётся только его существенные части. Если попытаемся запрограммировать всё мы потонем, поэтому абстракция помогает нашему мозгу впихнуть невпихуемое, как это умеют делают военные (а программисты пока нет):



Наши проблемы идут от мозга и от того, как устроена наша память. В отличие от хорошего хранилища долговременной памяти, кратковременная устроена по типу stack:
  • Есть входы и выходы;
  • TTL объекта около 20 секунд;
  • Удерживается очень мало объектов. Раньше считали, что оперативно человек оперирует 72 объектами (George Miller, 1989). Потом поняли, что это число еще меньше: 41 объект (Cowan, 2001) и вообще зависит от объектов.
  • Если мы что-то хотим держать в памяти дольше, то нам нужно сконцентрироваться и использовать повторение:



Еще мы используем Chunking (группировку) всякий раз, когда важно запомнить что-то большое. Например, чтобы запомнить число 88003334434, мы разделим его на группы по типу телефонного номера: 8-800-333-44-34. Для нашего мозга получится 5 объектов, которые запомнить легче, чем пытаться удержать число целиком или отдельно каждую его часть.

В коде мы используем слои, чтобы облегчить эту задачу. Однако пять таких слоев в голове удержать становится сложно, а больше пяти уже проблема. Для лучшего представления это можно сравнить с неправильной супер-лазаньей, в которой слишком много слоёв и они все слиплись. И это гораздо хуже, чем спагетти потому что спагетти мы можем отрефакторить и что-то нормальное в результате получить. А чтобы получить что-то нормальное из такой лазаньи, её надо сначала растерзать и превратить в понятные и очевидные спагетти, а потом заново собирать лазанью, только уже правильную.

Поэтому, чтобы совладать с неподъёмно сложными системами, изобретают архитектуру, используя абстракцию. Это не цель, а инструмент, и одновременно необходимое зло, так как все эти классы, супер-паттерны и прочие шутки не от нашей хорошей жизни.

Но как построить абстракцию, не сделав хуже?


Существует два понятия: cohesion (связность) и coupling (связанность). Они относятся в первую очередь к классам, но в целом и ко всем остальным сущностям. Разницу мало кто видит, так как звучат они почти одинаково.

И, хотя оба означают связь, coupling понимают в негативном ключе. Один объект завязан на другой в плохом смысле, если, ломая один из объектов, ломается всё остальное по цепочке. Cohesion несёт в себе позитивную ноту. Это группировка, в которой то, что близко по смыслу лежит в одном месте и взаимодействует примерно с теми же местами, опять же близкими по смыслу.

Для того, чтобы понять, coupling у вас или cohesion, существуют проверочные правила. Их сформулировал инженер и специалист в области информатики Роберт Мартин еще в 2000 году, и это принципы SOLID:
  • SRP;
  • OCP;
  • LSP;
  • ISP;
  • DIP.

Они, в общем-то, тоже немного расплывчатые и потому часто плохо понимаемые. Но сегодня я хочу поговорить не о них, а о пакетах.

Что есть пакет?


Пакет это группа единиц кода. Причем, пакеты это не обязательно пакеты 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 Пакеты должны быть сфокусированными. Использоваться должно всё. То есть классы, которые используются вместе упаковываем вместе. Проверочное правило здесь такое: смотрим, используется ли в нашем софте всё из того пакета, который к нему подключен. Если используется чуть-чуть, значит, скорее всего, пакет спроектирован неверно.

Эти три принципа дают понимание, как пакеты дизайнить. И казалось бы, нормально делай нормально будет. Однако реальность сурова, и я сейчас объясню почему. Вспомним треугольник от Артемия Лебедева, который вершины быстро, дёшево и качественно обозначил несколько другими словами. Такой же треугольник нарисовали и для пакетных принципов в Институте Макса Планка:



Получается, эти принципы конфликтуют, и в зависимости от того, какие стороны треугольника мы выбираем, вылезают соответствующие косяки:
  • Если мы группируем для пользователей (REP) и для мейнтенера (CCP), то получаем множество ненужных релизов: новые версии пакетов начинают вылетать как из пулемета. И пакет как тот же Chromе достигает версии 46 за полгода, когда все остальные браузеры выпускают одну мажорную версию раз в 7 лет.
  • Если мы группируем для пользователей (REP) и выделяем классы в пакеты по признаку переиспользования (CRP), у нас получаются изменения в туче пакетов. А это неудобно мейнтенеру, потому что приходится лезть в каждый из пакетов, и не получается релизить их по отдельности. Это дикая головная боль.
  • Если мы группируем для мейнтенера, то есть соблюдаем CCP и CRP, то получается всё круто для человека, который поддерживает этот пакет, но не круто для юзера, потому что переиспользовать такие пакеты получается плохо: они выходят как всякие мелкие штучки, которые собрать вместе просто нереально.

К сожалению, получить сразу и то, и другое, и третье не удастся, поэтому фокусироваться надо на CCP и REP по крайней мере, на начальных стадиях дизайна пакета. Да, у вас будет много релизов на старте, но это не так страшно, хотя и выглядит, конечно, не очень. Сейчас уже есть инструменты и для автоматизации упаковки пакетов и, собственно, выпуска версий.

Теперь переходим к принципам использования.

4 принцип ADP (Acyclic Dependencies Principle)


The dependency graph of packages must have no cycles Если есть циклические зависимости, то проблема вызывает лавину. Если есть циклы, то есть зависимость пакета зависит от самого пакета прямо или косвенно, то косяк в одном пакете вызывает лавину во всех остальных пакетах, и ломается абсолютно всё. К тому же, такие пакеты очень тяжело релизить.

Поэтому надо проверять, есть ли циклы. Для этого необходимо строить направленный граф зависимостей и смотреть на него. Руками это делать не очень удобно, поэтому для PHP есть библиотека clue/graph-composer, которой скармливаешь пакет, и она строит гигантский граф со всеми зависимостями. Смотреть на это, конечно, невозможно, поэтому надо зайти в PR#45, зачекаутить его и выбрать возможность исключать зависимости, которые не интересны. Допустим, если вы пишите фреймворк, то вам скорее всего интересны зависимости на свои пакеты, а чужие не так сильно, ведь свои косяки поправить можем, чужие тяжелее. И получается вот такой граф:


Если мы видим как здесь что циклических зависимостей нет, то всё отлично. Если есть, надо исправлять. Чем меньше зависимостей, тем проще.

Как разорвать цикл?


  1. DIP использовать инверсию зависимостей через интерфейсы. Мы должны ввести интерфейс и на него завязаться вместо зависимости на конкретные реализации.
  2. CRP выделить общий пакет. Например, есть кеш и с адаптерами. Чтобы развязать между собою Redis, базу и так далее выделяем драйверы в отдельные пакеты и выделяем сам общий пакет, в котором лежит только сам интерфейс. Это выглядит ужасно получается такой бесполезный пакет. Но с точки зрения DIP и CRP это будет правильно. И помимо того, что реально не будет ломаться, еще и даст нам крутой профит мы можем писать под этот пакет свои реализации.
  3. Переделать...

5 принцип SDP (Stable Dependencies Principle)


Это принцип стабильных зависимостей: Depend in the direction of stability Не получится строить стабильное на нестабильном. Нестабильность считается так:



Если на нас завязалось очень много всего скорее всего, мы стабильны. Если же мы завязались на много всего, то, очевидно, мы не очень стабильны. Как повысить стабильность? Следующим принципом.

6 принцип SAP (Stable Abstraction Principle)


Принцип стабильных абстракций гласит A package abstractness should increase with stability Стабильные пакеты абстрактны / Гибкие конкретны. То есть абстрактность должна возрастать со стабильностью. Стабильность здесь то, как часто нам приходится менять части пакета: классы, интерфейсы, или что-либо ещё. Абстрактные пакеты должны быть стабильны, чтобы безболезненно на них завязываться. В примере с тем же кэшем пакет с интерфейсом будем сверхстабильным, потому что менять интерфейс, про который мы договорились и хорошо над ним подумали скорее всего, не придётся. Если мы, конечно, абстрагируем не СУБД.

А вот гибкие пакеты, наоборот, конкретны они должны быть нестабильны, чтобы мы их легко меняли. Все наши приложения, наши конечные пакеты это на самом деле дико нестабильная штуковина, которая зависит от тучи всего и ломается чаще, чем пакеты, от которых она зависит.

Можно ли измерить абстрактность?


Конечно. Абстрактность это число классов и интерфейсов в пакете, деленное на число абстрактных классов и интерфейсов в этом самом пакете. То есть количество конкретного и абстрактного, деленное на количество абстрактного:


Еще есть такой полезный показатель, как D-метрика, в которой по вертикали нестабильность, а по горизонтали абстрактность. По двум зонам вверху справа и внизу слева мы можем понять:
  • Если стабильно, но не абстрактно это подозрительно.
  • Если дико нестабильное и очень абстрактное значит кто-то играется с интерфейсами и делает нашу жизнь адом.
  • Но иногда бывает 0,0 когда супер-неабстрактно и супер-стабильно, как в случае с хелперами или стандартными библиотеками PHP типа stdlib, strings, arrays и это будет нормально.



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


Для PHP есть 2 инструмента для того, чтобы посмотреть метрику своих пакетов:
  • PHP_Depend;
  • PhpMetrics.

Конечно, если вы захотите ими прогнать корневой пакет со своим приложением, вы увидите не 5 точек, как на схеме у вас вся схема будет в точках. Но из этой массы у вас будут выпрыгивать рыжие точки, при наведении на которые будет понятно, что это за классы вот с ними, скорее всего, и будут некоторые проблемы.

Как и SOLID, все эти дополнительные принципы и метрики не догма, но могут быть весьма полезными.

Резюме


Правильное проектирование пакетов вызывает взрывное дробление. Выглядит это страшно. Когда мы начали разрабатывать фреймворк Yii3 и выделять пакеты, у нас через некоторое время количество пакетов с 20 штук перевалило за сотню. Но если поначалу нас это насторожило, то позже мы поняли, что всё, тем не менее, работает стабильно Роберт Мартин был прав, и его принципы работают.

Конечно, есть области, в которых можно сознательно игнорировать все эти принципы, отдавая себе отчёт в том, что за этим стоит и где вы можете получить проблемы.

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

Купить билеты можно тут.

Хотите получить материалы с предыдущей конференции? Подписывайтесь на нашу рассылку.
Источник: habr.com
К списку статей
Опубликовано: 17.03.2021 10:08:48
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании конференции олега бунина (онтико)

Высокая производительность

Разработка веб-сайтов

Php

Программирование

Phprussia

Ооп

Пакеты

Магия php

Магия ооп

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru