1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-04-11 11:02:05 +02:00

Изоляция уровней абстракции

This commit is contained in:
Sergey Konstantinov 2015-04-05 13:47:38 +03:00
parent d5d2f385d3
commit 806e848f20

View File

@ -219,6 +219,78 @@
<p>Чем универсальнее ваше api в смысле широты тех контекстов, которые вы связываете, тем больше альтернативных реализаций одного и того функционала вам придётся реализовать (см. Пример с растровыми и векторными изображениями) и тем "шире" окажется ваша иерархия уровней абстракции: у вас появится множество объектов, принадлежащих одному уровню; в достаточно большом api неизбежно возникнут поддеревья иерархии - каждая реализация будет опираться на свой, полностью или частично, независимый слой логики.</p>
<h3>Изоляция уровней абстракции</h3>
<p>Для эффективной работы с такой сложной и разветвлённой иерархией необходимо добиваться изоляции её уровней, как вертикальной, так и горизонтальной. Попробуем разобраться на примере.</p>
<p>Допустим, мы разрабатываем набор компонент для диспетчерских служб. У нас есть задача отражать на карте погоды движение транспортных средств клиента, и мы спроектировали примерно такие интерфейсы:</p>
<ul>
<li>Map - класс собственно карты;</li>
<li>Vehicle - класс-диспетчеризируемый объект;</li>
<li>Overlay - графическое представление объекта.</li>
</ul>
<p>Далее у нас возникает задача отображать перемещение транспортных средств по карте. Так эти данные поступают от клиента, нам необходимо предоставить внешний интерфейс для этого. Как это сделать? Мы примерно представляем, что у клиента будет реализован какой-то поток уведомлений о смене местоположения его транспортных объектов, и мы предоставим ему специальный объект source - сущность, чьей ответственностью является обновление информации об объектах.</p>
<p>Возникает вопрос, каким образом мы свяжем source — объект, хранящий знание о положениях объектов — и overlay — объект непосредственно это знание реализующий. Наивное решение выглядит примерно так:</p>
<code>source.addOverlay(overlay)</code>
<p>При вызове этого метода source сохранит ссылку на overlay; при поступлении свежих данных опросит все оверлеи, выяснит идентификатор их родительского vehicle, и, если для данного vehicle пришла обновленная позиция, установит её исходному оверлею.</p>
<p>Хотя на бумаге всё выглядит гладко, нарушение принципов построения уровней абстракции очевидно:</p>
<ul>
<li>объект source, имплементирующий логику в терминах клиента, должен быть объектом высшего уровня в иерархии, наравне с Map;</li>
<li>соответственно, source не должен работать с объектами низшего уровня - оверлеями, - которые, к тому же, инстанцирует не он;</li>
<li>кроме того, знание о пиксельных координатах объекта является совершенно лишним для source - как объект высокого уровня он должен работать с информацией в терминах клиента - то есть с идентификаторами и географическими координатами.</li>
</ul>
<p>Чем же нам грозит подобный неправильный интерфейс? Давайте разберёмся.</p>
<p>Мы заставляем высокоуровневый объект преобразовывать геокоординаты в пиксельные. Очевидно, для этого source должен будет обратиться в map и выяснить множество информации, совершенно source ненужной - проекцию и масштаб карты, например. Таким образом, в нашей схеме source обладает знанием о всех прочих объектах нашего api - map, vehicle, overlay.</p>
<p>Чем плоха такая сильная связность? Перечислим основные неприятности, которые поджидают нас на этом пути.</p>
<p>Во-первых, мы задумали source как объект доступа к пользовательским данным. Так как у каждого пользователя обязательно будет своя специфика доступа к данным, то ситуации, когда пользователю нужно будет написать полностью или частично собственную реализацию source, будут возникать регулярно. Прикиньте теперь объём кода, который для этого потребуется: вместо того, чтобы написать адаптер для выдачи пар идентификатор-координаты, пользователю нужно будет реализовать взаимодействие с каждым компонентом api!</p>
<p>В той же ситуации окажемся и мы, если решим сами реализовать какие-то типовые варианты source. В лучшем случае нам придется заняться редактором для выделения общих компонент по взаимодействию с другими сущностями, в худшем - пользоваться методом copy-paste.</p>
<p>Хуже всего здесь то, операция преобразования координат - логически сложная вещь, вряд ли разработчик будет доставать учебник по сферической тригонометрии и, скорее всего, просто найдёт где-нибудь готовый кусок кода. Ошибки в этом коде, которые там неизбежно есть, будут приводить к трудно отлаживаемым артефактам, опять же в силу слабого знакомства разработчика с кодом.</p>
<p>Во-вторых, любые изменения в интерфейсах любого компонента приведут нас к необходимости переписать и source тоже. Если, скажем, мы реализуем для карты возможность вращаться - нам придётся переписать и source, ведь преобразование геокоординат в пиксели сцены является его работой.</p>
<p>В-третьих, любая ошибка в реализации любой из компонент грозит транзитом через source затронуть работу других, напрямую несвязанных компонентов системы. Например, если где-то в получении параметров карты есть ошибка, то при пересчёте координат оверлеев произойдёт исключение, и обновление координат vehicle не произойдёт или произойдёт частично - несмотря на то, что эти операции логически никак не связаны. Таким образом, отказоустойчивость системы и её плавная деградация при отказе одного из компонентов существенно снижается.</p>
<p>В-четвёртых, тестировать такой код будет существенно сложнее - и нам при его имплементации, и сторонним разработчикам при написании своих компонент, поскольку гораздо сложнее тестировать и сам source (много разнородной функциональности и множество зависимостей, которые придётся подменять "заглушками"-mock-ами), и связанные с ним объекты (поскольку написать mock на source тоже нетривиально).</p>
<p>Если суммировать вышесказанное, то можно выделить основные проблемы, которые несёт за собой неправильное выделение уровней абстракции:</p>
<ul>
<li>чрезмерное усложнение реализации альтернативных вариантов чересчур сильно связанных сущностей;</li>
<li>распространение ошибок по связанным сущностям и общее снижение отказоустойчивости системы;</li>
<li>усложнение работы с системой для внешних разработчиков, необходимость обладания экспертизой в областях, вообще говоря, напрямую не связанных с решаемой задачей;</li>
<li>усложнение тестирования кода как самого api, так и написанного поверх него;</li>
<li>необходимость постоянных рефакторингов и копи-паста кода.</li>
</ul>
<p>В следующем разделе мы попробуем спроектировать конкретные интерфейсы так, чтобы избежать перечисленных проблем.</p>
<!--
<h2>О проектировании API</h2>
<h3>Архитектура против реализации</h3>